Skip to content
Navigation Menu
{{ message }}
forked from huangzworks/annotated_redis_source
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscripting.c
More file actions
1203 lines (1071 loc) · 41.3 KB
/
Copy pathscripting.c
File metadata and controls
1203 lines (1071 loc) · 41.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "redis.h"
#include "sha1.h"
#include "rand.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <ctype.h>
#include <math.h>
char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
int redis_math_random (lua_State *L);
int redis_math_randomseed (lua_State *L);
void sha1hex(char *digest, char *script, size_t len);
/* Take a Redis reply in the Redis protocol format and convert it into a
* Lua type. Thanks to this function, and the introduction of not connected
* clients, it is trvial to implement the redis() lua function.
*
* Basically we take the arguments, execute the Redis command in the context
* of a non connected client, then take the generated reply and convert it
* into a suitable Lua type. With this trick the scripting feature does not
* need the introduction of a full Redis internals API. Basically the script
* is like a normal client that bypasses all the slow I/O paths.
*
* Note: in this function we do not do any sanity check as the reply is
* generated by Redis directly. This allows us to go faster.
* The reply string can be altered during the parsing as it is discared
* after the conversion is completed.
*
* Errors are returned as a table with a single 'err' field set to the
* error string.
*/
char *redisProtocolToLuaType(lua_State *lua, char* reply) {
char *p = reply;
switch(*p) {
case ':':
p = redisProtocolToLuaType_Int(lua,reply);
break;
case '$':
p = redisProtocolToLuaType_Bulk(lua,reply);
break;
case '+':
p = redisProtocolToLuaType_Status(lua,reply);
break;
case '-':
p = redisProtocolToLuaType_Error(lua,reply);
break;
case '*':
p = redisProtocolToLuaType_MultiBulk(lua,reply);
break;
}
return p;
}
char *redisProtocolToLuaType_Int(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
long long value;
string2ll(reply+1,p-reply-1,&value);
lua_pushnumber(lua,(lua_Number)value);
return p+2;
}
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
long long bulklen;
string2ll(reply+1,p-reply-1,&bulklen);
if (bulklen == -1) {
lua_pushboolean(lua,0);
return p+2;
} else {
lua_pushlstring(lua,p+2,bulklen);
return p+2+bulklen+2;
}
}
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
lua_newtable(lua);
lua_pushstring(lua,"ok");
lua_pushlstring(lua,reply+1,p-reply-1);
lua_settable(lua,-3);
return p+2;
}
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
lua_newtable(lua);
lua_pushstring(lua,"err");
lua_pushlstring(lua,reply+1,p-reply-1);
lua_settable(lua,-3);
return p+2;
}
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
p += 2;
if (mbulklen == -1) {
lua_pushboolean(lua,0);
return p;
}
lua_newtable(lua);
for (j = 0; j < mbulklen; j++) {
lua_pushnumber(lua,j+1);
p = redisProtocolToLuaType(lua,p);
lua_settable(lua,-3);
}
return p;
}
void luaPushError(lua_State *lua, char *error) {
lua_newtable(lua);
lua_pushstring(lua,"err");
lua_pushstring(lua, error);
lua_settable(lua,-3);
}
/* Sort the array currently in the stack. We do this to make the output
* of commands like KEYS or SMEMBERS something deterministic when called
* from Lua (to play well with AOf/replication).
*
* 对栈中的数组进行排序,从而让 KEYS 、 SMEMBERS 这样的命令的输出变得确定性。
*
* The array is sorted using table.sort itself, and assuming all the
* list elements are strings.
*
* 排序使用 table.sort 函数,并假设所有列表元素均为字符串
*/
void luaSortArray(lua_State *lua) {
/* Initial Stack: array */
lua_getglobal(lua,"table");
lua_pushstring(lua,"sort");
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
if (lua_pcall(lua,1,0,0)) {
/* Stack: array, table, error */
/* We are not interested in the error, we assume that the problem is
* that there are 'false' elements inside the array, so we try
* again with a slower function but able to handle this case, that
* is: table.sort(table, __redis__compare_helper) */
lua_pop(lua,1); /* Stack: array, table */
lua_pushstring(lua,"sort"); /* Stack: array, table, sort */
lua_gettable(lua,-2); /* Stack: array, table, table.sort */
lua_pushvalue(lua,-3); /* Stack: array, table, table.sort, array */
lua_getglobal(lua,"__redis__compare_helper");
/* Stack: array, table, table.sort, array, __redis__compare_helper */
lua_call(lua,2,0);
}
/* Stack: array (sorted), table */
lua_pop(lua,1); /* Stack: array (sorted) */
}
/*
* 用于在 Lua 环境中执行 Redis 命令
*/
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
int j, argc = lua_gettop(lua);
struct redisCommand *cmd;
robj **argv;
// 指向伪客户端
redisClient *c = server.lua_client;
sds reply;
/* Require at least one argument */
// 参数检查
if (argc == 0) {
luaPushError(lua,
"Please specify at least one argument for redis.call()");
return 1;
}
/* Build the arguments vector */
// 构建参数数组
argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) {
if (!lua_isstring(lua,j+1)) break;
argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
lua_strlen(lua,j+1));
}
/* Check if one of the arguments passed by the Lua script
* is not a string or an integer (lua_isstring() return true for
* integers as well). */
// 参数只能是字符串或者整数
if (j != argc) {
j--;
while (j >= 0) {
decrRefCount(argv[j]);
j--;
}
zfree(argv);
luaPushError(lua,
"Lua redis() command arguments must be strings or integers");
return 1;
}
/* Setup our fake client for command execution */
// 设置参数和参数数量
c->argv = argv;
c->argc = argc;
/* Command lookup */
// 查找命令实现函数
cmd = lookupCommand(argv[0]->ptr);
if (!cmd || ((cmd->arity > 0 && cmd->arity != argc) ||
(argc < -cmd->arity)))
{
if (cmd)
luaPushError(lua,
"Wrong number of args calling Redis command From Lua script");
else
luaPushError(lua,"Unknown Redis command called from Lua script");
goto cleanup;
}
/* There are commands that are not allowed inside scripts. */
// 只能执行被允许在 Lua 脚本中执行的函数
if (cmd->flags & REDIS_CMD_NOSCRIPT) {
luaPushError(lua, "This Redis command is not allowed from scripts");
goto cleanup;
}
/* Write commands are forbidden against read-only slaves, or if a
* command marked as non-deterministic was already called in the context
* of this script. */
if (cmd->flags & REDIS_CMD_WRITE) {
if (server.lua_random_dirty) {
// 不能在执行过随机命令之后执行写命令
luaPushError(lua,
"Write commands not allowed after non deterministic commands");
goto cleanup;
} else if (server.masterhost && server.repl_slave_ro &&
!(server.lua_caller->flags & REDIS_MASTER))
{
// 不能在附属节点执行
luaPushError(lua, shared.roslaveerr->ptr);
goto cleanup;
} else if (server.stop_writes_on_bgsave_err &&
server.saveparamslen > 0 &&
server.lastbgsave_status == REDIS_ERR)
{
// 不能在 Redis 已出错的环境下执行
luaPushError(lua, shared.bgsaveerr->ptr);
goto cleanup;
}
}
/* If we reached the memory limit configured via maxmemory, commands that
* could enlarge the memory usage are not allowed, but only if this is the
* first write in the context of this script, otherwise we can't stop
* in the middle. */
// 如果是第一次执行写操作,并且遭遇内存超过,那么退出
if (server.maxmemory && server.lua_write_dirty == 0 &&
(cmd->flags & REDIS_CMD_DENYOOM))
{
if (freeMemoryIfNeeded() == REDIS_ERR) {
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
}
// 如果是随机操作,设置 lua_random_dirty 状态
if (cmd->flags & REDIS_CMD_RANDOM) server.lua_random_dirty = 1;
// 如果是写操作,设置 lua_write_dirty 状态
if (cmd->flags & REDIS_CMD_WRITE) server.lua_write_dirty = 1;
/* Run the command */
// 执行命令
c->cmd = cmd;
call(c,REDIS_CALL_SLOWLOG | REDIS_CALL_STATS);
/* Convert the result of the Redis command into a suitable Lua type.
* The first thing we need is to create a single string from the client
* output buffers. */
// 取出回复
reply = sdsempty();
if (c->bufpos) {
reply = sdscatlen(reply,c->buf,c->bufpos);
c->bufpos = 0;
}
while(listLength(c->reply)) {
robj *o = listNodeValue(listFirst(c->reply));
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
listDelNode(c->reply,listFirst(c->reply));
}
if (raise_error && reply[0] != '-') raise_error = 0;
// 将回复转换为 Lua 值,并返回给 Lua 环境
redisProtocolToLuaType(lua,reply);
/* Sort the output array if needed, assuming it is a non-null multi bulk
* reply as expected. */
// 看是否需要执行排序
if ((cmd->flags & REDIS_CMD_SORT_FOR_SCRIPT) &&
(reply[0] == '*' && reply[1] != '-')) {
luaSortArray(lua);
}
sdsfree(reply);
c->reply_bytes = 0;
cleanup:
/* Clean up. Command code may have changed argv/argc so we use the
* argv/argc of the client instead of the local variables. */
for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]);
zfree(c->argv);
if (raise_error) {
/* If we are here we should have an error in the stack, in the
* form of a table with an "err" field. Extract the string to
* return the plain error. */
lua_pushstring(lua,"err");
lua_gettable(lua,-2);
return lua_error(lua);
}
return 1;
}
int luaRedisCallCommand(lua_State *lua) {
return luaRedisGenericCommand(lua,1);
}
int luaRedisPCallCommand(lua_State *lua) {
return luaRedisGenericCommand(lua,0);
}
/* This adds redis.sha1hex(string) to Lua scripts using the same hashing
* function used for sha1ing lua scripts. */
int luaRedisSha1hexCommand(lua_State *lua) {
int argc = lua_gettop(lua);
char digest[41];
size_t len;
char *s;
if (argc != 1) {
luaPushError(lua, "wrong number of arguments");
return 1;
}
s = (char*)lua_tolstring(lua,1,&len);
sha1hex(digest,s,len);
lua_pushstring(lua,digest);
return 1;
}
/* Returns a table with a single field 'field' set to the string value
* passed as argument. This helper function is handy when returning
* a Redis Protocol error or status reply from Lua:
*
* return redis.error_reply("ERR Some Error")
* return redis.status_reply("ERR Some Error")
*/
int luaRedisReturnSingleFieldTable(lua_State *lua, char *field) {
if (lua_gettop(lua) != 1 || lua_type(lua,-1) != LUA_TSTRING) {
luaPushError(lua, "wrong number or type of arguments");
return 1;
}
lua_newtable(lua);
lua_pushstring(lua, field);
lua_pushvalue(lua, -3);
lua_settable(lua, -3);
return 1;
}
int luaRedisErrorReplyCommand(lua_State *lua) {
return luaRedisReturnSingleFieldTable(lua,"err");
}
int luaRedisStatusReplyCommand(lua_State *lua) {
return luaRedisReturnSingleFieldTable(lua,"ok");
}
int luaLogCommand(lua_State *lua) {
int j, argc = lua_gettop(lua);
int level;
sds log;
if (argc < 2) {
luaPushError(lua, "redis.log() requires two arguments or more.");
return 1;
} else if (!lua_isnumber(lua,-argc)) {
luaPushError(lua, "First argument must be a number (log level).");
return 1;
}
level = lua_tonumber(lua,-argc);
if (level < REDIS_DEBUG || level > REDIS_WARNING) {
luaPushError(lua, "Invalid debug level.");
return 1;
}
/* Glue together all the arguments */
log = sdsempty();
for (j = 1; j < argc; j++) {
size_t len;
char *s;
s = (char*)lua_tolstring(lua,(-argc)+j,&len);
if (s) {
if (j != 1) log = sdscatlen(log," ",1);
log = sdscatlen(log,s,len);
}
}
redisLogRaw(level,log);
sdsfree(log);
return 0;
}
/*
* 脚本超时钩子
*/
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
long long elapsed;
REDIS_NOTUSED(ar);
REDIS_NOTUSED(lua);
// 计算已执行时间
elapsed = (ustime()/1000) - server.lua_time_start;
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
redisLog(REDIS_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
// 已超时
server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
* we need to mask the client executing the script from the event loop.
* If we don't do that the client may disconnect and could no longer be
* here when the EVAL command will return. */
// 当脚本运行超时时,将正在执行的客户端从读事件中移除
// 并允许其他客户端执行 SCRIPT KILL 或者 SHUTDOWN NOSAVE
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
}
if (server.lua_timedout)
// 在脚本上下文中,启动文件事件处理(等待 SCRIPT KILL 或 SHUTDOWN NOSAVE)
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
if (server.lua_kill) {
// 杀死脚本
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
lua_error(lua);
}
}
/*
* 将指定函数以指定名字载入到 Lua 环境,并调用
*/
void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) {
lua_pushcfunction(lua, luafunc);
lua_pushstring(lua, libname);
lua_call(lua, 1, 0);
}
LUALIB_API int (luaopen_cjson) (lua_State *L);
LUALIB_API int (luaopen_struct) (lua_State *L);
LUALIB_API int (luaopen_cmsgpack) (lua_State *L);
/*
* 将指定的库载入到 Lua 环境中
*/
void luaLoadLibraries(lua_State *lua) {
// 基础库
luaLoadLib(lua, "", luaopen_base);
// table 库
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
// 字符串库
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
// 数学库
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
// debug 库
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
// cjson 库
luaLoadLib(lua, "cjson", luaopen_cjson);
// struct 库
luaLoadLib(lua, "struct", luaopen_struct);
// cmsgpack 库
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
#if 0 /* Stuff that we don't load currently, for sandboxing concerns. */
luaLoadLib(lua, LUA_LOADLIBNAME, luaopen_package);
luaLoadLib(lua, LUA_OSLIBNAME, luaopen_os);
#endif
}
/* Remove a functions that we don't want to expose to the Redis scripting
* environment.
*
* 移除所有不想暴露给 Redis 脚本的函数
*/
void luaRemoveUnsupportedFunctions(lua_State *lua) {
// 屏蔽 loadfile
lua_pushnil(lua);
lua_setglobal(lua,"loadfile");
}
/* This function installs metamethods in the global table _G that prevent
* the creation of globals accidentally.
*
* It should be the last to be called in the scripting engine initialization
* sequence, because it may interact with creation of globals. */
void scriptingEnableGlobalsProtection(lua_State *lua) {
char *s[32];
sds code = sdsempty();
int j = 0;
/* strict.lua from: http://metalua.luaforge.net/src/lib/strict.lua.html.
* Modified to be adapted to Redis. */
s[j++]="local mt = {}\n";
s[j++]="setmetatable(_G, mt)\n";
s[j++]="mt.__newindex = function (t, n, v)\n";
s[j++]=" if debug.getinfo(2) then\n";
s[j++]=" local w = debug.getinfo(2, \"S\").what\n";
s[j++]=" if w ~= \"main\" and w ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to create global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n";
s[j++]=" end\n";
s[j++]=" rawset(t, n, v)\n";
s[j++]="end\n";
s[j++]="mt.__index = function (t, n)\n";
s[j++]=" if debug.getinfo(2) and debug.getinfo(2, \"S\").what ~= \"C\" then\n";
s[j++]=" error(\"Script attempted to access unexisting global variable '\"..tostring(n)..\"'\", 2)\n";
s[j++]=" end\n";
s[j++]=" return rawget(t, n)\n";
s[j++]="end\n";
s[j++]=NULL;
for (j = 0; s[j] != NULL; j++) code = sdscatlen(code,s[j],strlen(s[j]));
luaL_loadbuffer(lua,code,sdslen(code),"@enable_strict_lua");
lua_pcall(lua,0,0,0);
sdsfree(code);
}
/* Initialize the scripting environment.
*
* 初始化脚本环境。
*
* It is possible to call this function to reset the scripting environment
* assuming that we call scriptingRelease() before.
*
* 这个函数也可以在 scriptingRelease() 执行之后使用,用于对脚本环境进行重置。
*
* See scriptingReset() for more information.
*/
void scriptingInit(void) {
// 创建一个新的 lua 环境
lua_State *lua = lua_open();
// 载入内置函数库
luaLoadLibraries(lua);
// 屏蔽所有不想暴露给脚本环境的函数(目前只有 loadfile)
luaRemoveUnsupportedFunctions(lua);
/* Initialize a dictionary we use to map SHAs to scripts.
* 创建一个字典,用于将 SHA 校验码映射到脚本
* This is useful for replication, as we need to replicate EVALSHA
* as EVAL, so we need to remember the associated script.
* 因为 Redis 目前复制脚本的方法是将 EVALSHA 转换成 EVAL 来进行的,
* 所以程序需要保存和 SHA 校验值相对应的脚本。
*/
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
/* Register the redis commands table and fields */
// 创建 table ,并以给定名称将 C 函数注册到表格上
lua_newtable(lua);
/* 注册 redis.call */
lua_pushstring(lua,"call");
lua_pushcfunction(lua,luaRedisCallCommand);
lua_settable(lua,-3);
/* 注册 redis.pcall */
lua_pushstring(lua,"pcall");
lua_pushcfunction(lua,luaRedisPCallCommand);
lua_settable(lua,-3);
/* 注册 redis.log 和 log levels. */
lua_pushstring(lua,"log");
lua_pushcfunction(lua,luaLogCommand);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_DEBUG");
lua_pushnumber(lua,REDIS_DEBUG);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_VERBOSE");
lua_pushnumber(lua,REDIS_VERBOSE);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_NOTICE");
lua_pushnumber(lua,REDIS_NOTICE);
lua_settable(lua,-3);
lua_pushstring(lua,"LOG_WARNING");
lua_pushnumber(lua,REDIS_WARNING);
lua_settable(lua,-3);
/* 注册 redis.sha1hex */
lua_pushstring(lua, "sha1hex");
lua_pushcfunction(lua, luaRedisSha1hexCommand);
lua_settable(lua, -3);
/* 注册 redis.error_reply and redis.status_reply */
lua_pushstring(lua, "error_reply");
lua_pushcfunction(lua, luaRedisErrorReplyCommand);
lua_settable(lua, -3);
lua_pushstring(lua, "status_reply");
lua_pushcfunction(lua, luaRedisStatusReplyCommand);
lua_settable(lua, -3);
/* Finally set the table as 'redis' global var. */
// 将 table 设置为 redis 全局变量
lua_setglobal(lua,"redis");
/* Replace math.random and math.randomseed with our implementations. */
// 将 math.random 和 math.randomseed 设置为 Redis 实现
// 用于生成确定性的随机数
// (对于同一个 seed ,生成的随机序列总是相同)
lua_getglobal(lua,"math");
lua_pushstring(lua,"random");
lua_pushcfunction(lua,redis_math_random);
lua_settable(lua,-3);
lua_pushstring(lua,"randomseed");
lua_pushcfunction(lua,redis_math_randomseed);
lua_settable(lua,-3);
lua_setglobal(lua,"math");
/* Add a helper funciton that we use to sort the multi bulk output of non
* deterministic commands, when containing 'false' elements.
* 用于对不确定性多个回复的输出进行排序
*/
{
char *compare_func = "function __redis__compare_helper(a,b)\n"
" if a == false then a = '' end\n"
" if b == false then b = '' end\n"
" return a<b\n"
"end\n";
luaL_loadbuffer(lua,compare_func,strlen(compare_func),"@cmp_func_def");
lua_pcall(lua,0,0,0);
}
/* Create the (non connected) client that we use to execute Redis commands
* inside the Lua interpreter.
* 创建一个无网络连接的伪客户端,用于执行 Lua 脚本中的命令
* Note: there is no need to create it again when this function is called
* by scriptingReset().
*/
if (server.lua_client == NULL) {
server.lua_client = createClient(-1);
server.lua_client->flags |= REDIS_LUA_CLIENT;
}
/* Lua beginners ofter don't use "local", this is likely to introduce
* subtle bugs in their code. To prevent problems we protect accesses
* to global variables.
*
* 为了避免全局变量被意外地修改,保护全局变量。
*/
scriptingEnableGlobalsProtection(lua);
// 将 lua 环境赋值到服务器变量
server.lua = lua;
}
/* Release resources related to Lua scripting.
* 释放 Lua 脚本的相关资源
* This function is used in order to reset the scripting environment.
* 用于初始化脚本环境
*/
void scriptingRelease(void) {
// 释放记录脚本的字典
dictRelease(server.lua_scripts);
// 关闭 Lua 环境
lua_close(server.lua);
}
/*
* 重置脚本环境
*/
void scriptingReset(void) {
// 释放环境
scriptingRelease();
// 新键环境
scriptingInit();
}
/* Perform the SHA1 of the input string. We use this both for hasing script
* bodies in order to obtain the Lua function name, and in the implementation
* of redis.sha1().
*
* 计算输入字符串的 SHA1 校验和,用于计算函数名,也用于 redis.sha1()
*
* 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an
* hexadecimal number, plus 1 byte for null term. */
void sha1hex(char *digest, char *script, size_t len) {
SHA1_CTX ctx;
unsigned char hash[20];
char *cset = "0123456789abcdef";
int j;
SHA1Init(&ctx);
SHA1Update(&ctx,(unsigned char*)script,len);
SHA1Final(hash,&ctx);
for (j = 0; j < 20; j++) {
digest[j*2] = cset[((hash[j]&0xF0)>>4)];
digest[j*2+1] = cset[(hash[j]&0xF)];
}
digest[40] = '\0';
}
void luaReplyToRedisReply(redisClient *c, lua_State *lua) {
int t = lua_type(lua,-1);
switch(t) {
case LUA_TSTRING:
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
break;
case LUA_TBOOLEAN:
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
break;
case LUA_TNUMBER:
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
break;
case LUA_TTABLE:
/* We need to check if it is an array, an error, or a status reply.
* Error are returned as a single element table with 'err' field.
* Status replies are returned as single elment table with 'ok' field */
lua_pushstring(lua,"err");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TSTRING) {
sds err = sdsnew(lua_tostring(lua,-1));
sdsmapchars(err,"\r\n"," ",2);
addReplySds(c,sdscatprintf(sdsempty(),"-%s\r\n",err));
sdsfree(err);
lua_pop(lua,2);
return;
}
lua_pop(lua,1);
lua_pushstring(lua,"ok");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TSTRING) {
sds ok = sdsnew(lua_tostring(lua,-1));
sdsmapchars(ok,"\r\n"," ",2);
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
sdsfree(ok);
lua_pop(lua,1);
} else {
void *replylen = addDeferredMultiBulkLength(c);
int j = 1, mbulklen = 0;
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
while(1) {
lua_pushnumber(lua,j++);
lua_gettable(lua,-2);
t = lua_type(lua,-1);
if (t == LUA_TNIL) {
lua_pop(lua,1);
break;
}
luaReplyToRedisReply(c, lua);
mbulklen++;
}
setDeferredMultiBulkLength(c,replylen,mbulklen);
}
break;
default:
addReply(c,shared.nullbulk);
}
lua_pop(lua,1);
}
/* Set an array of Redis String Objects as a Lua array (table) stored into a
* global variable. */
// 创建一个数组,并将它设置为给定名字的全局变量
void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
int j;
lua_newtable(lua);
for (j = 0; j < elec; j++) {
lua_pushlstring(lua,(char*)elev[j]->ptr,sdslen(elev[j]->ptr));
lua_rawseti(lua,-2,j+1);
}
lua_setglobal(lua,var);
}
/* Define a lua function with the specified function name and body.
*
* 根据给定函数名和代码体(body),创建 Lua 函数。
*
* The function name musts be a 2 characters long string, since all the
* functions we defined in the Lua context are in the form:
*
* 所有函数名称的长度都必须大于 2 ,因为我们使用以下格式来创建函数名:
*
* f_<hex sha1 sum>
*
* On success REDIS_OK is returned, and nothing is left on the Lua stack.
* 创建成功返回 REDIS_OK ,并清除 Lua 栈中的所有资料。
*
* On error REDIS_ERR is returned and an appropriate error is set in the
* client context.
*/
int luaCreateFunction(redisClient *c, lua_State *lua, char *funcname, robj *body) {
sds funcdef = sdsempty();
// 函数定义
funcdef = sdscat(funcdef,"function ");
funcdef = sdscatlen(funcdef,funcname,42);
funcdef = sdscatlen(funcdef,"() ",3);
funcdef = sdscatlen(funcdef,body->ptr,sdslen(body->ptr));
funcdef = sdscatlen(funcdef," end",4);
// 将函数定义载入到 Lua 中(并编译),但不执行该定义
if (luaL_loadbuffer(lua,funcdef,sdslen(funcdef),"@user_script")) {
// 如果编译出错,那么返回错误
addReplyErrorFormat(c,"Error compiling script (new function): %s\n",
lua_tostring(lua,-1));
lua_pop(lua,1);
sdsfree(funcdef);
return REDIS_ERR;
}
sdsfree(funcdef);
// 定义函数
if (lua_pcall(lua,0,0,0)) {
addReplyErrorFormat(c,"Error running script (new function): %s\n",
lua_tostring(lua,-1));
lua_pop(lua,1);
return REDIS_ERR;
}
/* We also save a SHA1 -> Original script map in a dictionary
* so that we can replicate / write in the AOF all the
* EVALSHA commands as EVAL using the original script.
*
* 创建一个从 SHA1 映射到原始脚本的字典,
* 从而使得脚本可以用于复制,以及写入到 AOF 文件
*/
{
int retval = dictAdd(server.lua_scripts,
// SHA1 值,不包括前缀 f_
sdsnewlen(funcname+2,40),
// 代码体
body);
redisAssertWithInfo(c,NULL,retval == DICT_OK);
incrRefCount(body);
}
return REDIS_OK;
}
void evalGenericCommand(redisClient *c, int evalsha) {
lua_State *lua = server.lua;
char funcname[43];
long long numkeys;
int delhook = 0;
/* We want the same PRNG sequence at every call so that our PRNG is
* not affected by external state.
*
* 在每次执行 EVAL 时重置随机 seed ,从而保证可以生成相同的随机序列。
*/
redisSrand48(0);
/* We set this flag to zero to remember that so far no random command
* was called. This way we can allow the user to call commands like
* SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command
* is called (otherwise the replication and AOF would end with non
* deterministic sequences).
*
* Thanks to this flag we'll raise an error every time a write command
* is called after a random command was used.
*
* 用两个变量,对命令进行检查
* 确保在调用随机命令之后,再调用写命令将出现错误
*/
server.lua_random_dirty = 0;
server.lua_write_dirty = 0;
/* Get the number of arguments that are keys */
// 获取输入键的数量
if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK)
return;
// 对键的正确性做一个快速检查
if (numkeys > (c->argc - 3)) {
addReplyError(c,"Number of keys can't be greater than number of args");
return;
}
/* We obtain the script SHA1, then check if this function is already
* defined into the Lua state */
// 组合出函数的名字,例如 f_282297a0228f48cd3fc6a55de6316f31422f5d17
funcname[0] = 'f';
funcname[1] = '_';
if (!evalsha) {
/* Hash the code if this is an EVAL call */
// 如果执行的是 EVAL 命令,那么计算脚本的 SHA1 校验和
sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr));
} else {
// 如果执行的是 EVALSHA 命令,直接使用传入的 SHA1 值
/* We already have the SHA if it is a EVALSHA */
int j;
char *sha = c->argv[1]->ptr;
for (j = 0; j < 40; j++)
funcname[j+2] = tolower(sha[j]);
funcname[42] = '\0';
}
/* Try to lookup the Lua function */
// 按名查找函数
lua_getglobal(lua, funcname);
if (lua_isnil(lua,1)) {
// 没找到脚本相应的脚本
lua_pop(lua,1); /* remove the nil from the stack */
/* Function not defined... let's define it if we have the
* body of the funciton. If this is an EVALSHA call we can just
* return an error. */
if (evalsha) {
// 如果执行的是 EVALSHA ,返回脚本未找到错误
addReply(c, shared.noscripterr);
return;
}
// 如果执行的是 EVAL ,那么创建并执行新函数,然后将代码添加到脚本字典中
if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return;
/* Now the following is guaranteed to return non nil */
lua_getglobal(lua, funcname);
redisAssert(!lua_isnil(lua,1));
}
/* Populate the argv and keys table accordingly to the arguments that
* EVAL received. */
// 设置 KEYS 和 ARGV 全局变量到 Lua 环境
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
/* Select the right DB in the context of the Lua client */
// 为 Lua 所属的(伪)客户端设置数据库
selectDb(server.lua_client,c->db->id);
/* Set an hook in order to be able to stop the script execution if it
* is running for too much time.
*
* 设置一个钩子,用于在运行时间过长时停止脚本的运作。
*
* We set the hook only if the time limit is enabled as the hook will
* make the Lua script execution slower.
*
* 只在开启了时间限制选项时使用钩子,因为它会拖慢脚本的运行速度。
*/
You can’t perform that action at this time.
