关键词搜索

源码搜索 ×
×

漫话Redis源码之四十五

发布2022-01-02浏览441次

详情内容

这里主要讲述了Redis池分配的一些事情和操作。

在阅读源码的时候,需要大致注意下结构体的含义,以及嵌套在其中的逻辑关系。

一般来说,我看这些代码,只看大概意思,不注重细节。

  1. /* AutMemEntry type field values. */
  2. #define REDISMODULE_AM_KEY 0
  3. #define REDISMODULE_AM_STRING 1
  4. #define REDISMODULE_AM_REPLY 2
  5. #define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
  6. #define REDISMODULE_AM_DICT 4
  7. #define REDISMODULE_AM_INFO 5
  8. /* The pool allocator block. Redis Modules can allocate memory via this special
  9. * allocator that will automatically release it all once the callback returns.
  10. * This means that it can only be used for ephemeral allocations. However
  11. * there are two advantages for modules to use this API:
  12. *
  13. * 1) The memory is automatically released when the callback returns.
  14. * 2) This allocator is faster for many small allocations since whole blocks
  15. * are allocated, and small pieces returned to the caller just advancing
  16. * the index of the allocation.
  17. *
  18. * Allocations are always rounded to the size of the void pointer in order
  19. * to always return aligned memory chunks. */
  20. #define REDISMODULE_POOL_ALLOC_MIN_SIZE (1024*8)
  21. #define REDISMODULE_POOL_ALLOC_ALIGN (sizeof(void*))
  22. typedef struct RedisModulePoolAllocBlock {
  23. uint32_t size;
  24. uint32_t used;
  25. struct RedisModulePoolAllocBlock *next;
  26. char memory[];
  27. } RedisModulePoolAllocBlock;
  28. /* This structure represents the context in which Redis modules operate.
  29. * Most APIs module can access, get a pointer to the context, so that the API
  30. * implementation can hold state across calls, or remember what to free after
  31. * the call and so forth.
  32. *
  33. * Note that not all the context structure is always filled with actual values
  34. * but only the fields needed in a given context. */
  35. struct RedisModuleBlockedClient;
  36. struct RedisModuleCtx {
  37. void *getapifuncptr; /* NOTE: Must be the first field. */
  38. struct RedisModule *module; /* Module reference. */
  39. client *client; /* Client calling a command. */
  40. struct RedisModuleBlockedClient *blocked_client; /* Blocked client for
  41. thread safe context. */
  42. struct AutoMemEntry *amqueue; /* Auto memory queue of objects to free. */
  43. int amqueue_len; /* Number of slots in amqueue. */
  44. int amqueue_used; /* Number of used slots in amqueue. */
  45. int flags; /* REDISMODULE_CTX_... flags. */
  46. void **postponed_arrays; /* To set with RM_ReplySetArrayLength(). */
  47. int postponed_arrays_count; /* Number of entries in postponed_arrays. */
  48. void *blocked_privdata; /* Privdata set when unblocking a client. */
  49. RedisModuleString *blocked_ready_key; /* Key ready when the reply callback
  50. gets called for clients blocked
  51. on keys. */
  52. /* Used if there is the REDISMODULE_CTX_KEYS_POS_REQUEST flag set. */
  53. getKeysResult *keys_result;
  54. struct RedisModulePoolAllocBlock *pa_head;
  55. redisOpArray saved_oparray; /* When propagating commands in a callback
  56. we reallocate the "also propagate" op
  57. array. Here we save the old one to
  58. restore it later. */
  59. };
  60. typedef struct RedisModuleCtx RedisModuleCtx;
  61. #define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, NULL, NULL, {0}}
  62. #define REDISMODULE_CTX_AUTO_MEMORY (1<<0)
  63. #define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<1)
  64. #define REDISMODULE_CTX_BLOCKED_REPLY (1<<2)
  65. #define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<3)
  66. #define REDISMODULE_CTX_THREAD_SAFE (1<<4)
  67. #define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<5)
  68. #define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<6)
  69. #define REDISMODULE_CTX_MULTI_EMITTED (1<<7)
  70. /* This represents a Redis key opened with RM_OpenKey(). */
  71. struct RedisModuleKey {
  72. RedisModuleCtx *ctx;
  73. redisDb *db;
  74. robj *key; /* Key name object. */
  75. robj *value; /* Value object, or NULL if the key was not found. */
  76. void *iter; /* Iterator. */
  77. int mode; /* Opening mode. */
  78. union {
  79. struct {
  80. /* Zset iterator, use only if value->type == OBJ_ZSET */
  81. uint32_t type; /* REDISMODULE_ZSET_RANGE_* */
  82. zrangespec rs; /* Score range. */
  83. zlexrangespec lrs; /* Lex range. */
  84. uint32_t start; /* Start pos for positional ranges. */
  85. uint32_t end; /* End pos for positional ranges. */
  86. void *current; /* Zset iterator current node. */
  87. int er; /* Zset iterator end reached flag
  88. (true if end was reached). */
  89. } zset;
  90. struct {
  91. /* Stream, use only if value->type == OBJ_STREAM */
  92. streamID currentid; /* Current entry while iterating. */
  93. int64_t numfieldsleft; /* Fields left to fetch for current entry. */
  94. int signalready; /* Flag that signalKeyAsReady() is needed. */
  95. } stream;
  96. } u;
  97. };
  98. typedef struct RedisModuleKey RedisModuleKey;
  99. /* RedisModuleKey 'ztype' values. */
  100. #define REDISMODULE_ZSET_RANGE_NONE 0 /* This must always be 0. */
  101. #define REDISMODULE_ZSET_RANGE_LEX 1
  102. #define REDISMODULE_ZSET_RANGE_SCORE 2
  103. #define REDISMODULE_ZSET_RANGE_POS 3
  104. /* Function pointer type of a function representing a command inside
  105. * a Redis module. */
  106. struct RedisModuleBlockedClient;
  107. typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, void **argv, int argc);
  108. typedef void (*RedisModuleDisconnectFunc) (RedisModuleCtx *ctx, struct RedisModuleBlockedClient *bc);
  109. /* This struct holds the information about a command registered by a module.*/
  110. struct RedisModuleCommandProxy {
  111. struct RedisModule *module;
  112. RedisModuleCmdFunc func;
  113. struct redisCommand *rediscmd;
  114. };
  115. typedef struct RedisModuleCommandProxy RedisModuleCommandProxy;
  116. #define REDISMODULE_REPLYFLAG_NONE 0
  117. #define REDISMODULE_REPLYFLAG_TOPARSE (1<<0) /* Protocol must be parsed. */
  118. #define REDISMODULE_REPLYFLAG_NESTED (1<<1) /* Nested reply object. No proto
  119. or struct free. */
  120. /* Reply of RM_Call() function. The function is filled in a lazy
  121. * way depending on the function called on the reply structure. By default
  122. * only the type, proto and protolen are filled. */
  123. typedef struct RedisModuleCallReply {
  124. RedisModuleCtx *ctx;
  125. int type; /* REDISMODULE_REPLY_... */
  126. int flags; /* REDISMODULE_REPLYFLAG_... */
  127. size_t len; /* Len of strings or num of elements of arrays. */
  128. char *proto; /* Raw reply protocol. An SDS string at top-level object. */
  129. size_t protolen;/* Length of protocol. */
  130. union {
  131. const char *str; /* String pointer for string and error replies. This
  132. does not need to be freed, always points inside
  133. a reply->proto buffer of the reply object or, in
  134. case of array elements, of parent reply objects. */
  135. long long ll; /* Reply value for integer reply. */
  136. struct RedisModuleCallReply *array; /* Array of sub-reply elements. */
  137. } val;
  138. } RedisModuleCallReply;
  139. /* Structure representing a blocked client. We get a pointer to such
  140. * an object when blocking from modules. */
  141. typedef struct RedisModuleBlockedClient {
  142. client *client; /* Pointer to the blocked client. or NULL if the client
  143. was destroyed during the life of this object. */
  144. RedisModule *module; /* Module blocking the client. */
  145. RedisModuleCmdFunc reply_callback; /* Reply callback on normal completion.*/
  146. RedisModuleCmdFunc timeout_callback; /* Reply callback on timeout. */
  147. RedisModuleDisconnectFunc disconnect_callback; /* Called on disconnection.*/
  148. void (*free_privdata)(RedisModuleCtx*,void*);/* privdata cleanup callback.*/
  149. void *privdata; /* Module private data that may be used by the reply
  150. or timeout callback. It is set via the
  151. RedisModule_UnblockClient() API. */
  152. client *reply_client; /* Fake client used to accumulate replies
  153. in thread safe contexts. */
  154. int dbid; /* Database number selected by the original client. */
  155. int blocked_on_keys; /* If blocked via RM_BlockClientOnKeys(). */
  156. int unblocked; /* Already on the moduleUnblocked list. */
  157. monotime background_timer; /* Timer tracking the start of background work */
  158. uint64_t background_duration; /* Current command background time duration.
  159. Used for measuring latency of blocking cmds */
  160. } RedisModuleBlockedClient;
  161. static pthread_mutex_t moduleUnblockedClientsMutex = PTHREAD_MUTEX_INITIALIZER;
  162. static list *moduleUnblockedClients;
  163. /* We need a mutex that is unlocked / relocked in beforeSleep() in order to
  164. * allow thread safe contexts to execute commands at a safe moment. */
  165. static pthread_mutex_t moduleGIL = PTHREAD_MUTEX_INITIALIZER;
  166. /* Function pointer type for keyspace event notification subscriptions from modules. */
  167. typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
  168. /* Keyspace notification subscriber information.
  169. * See RM_SubscribeToKeyspaceEvents() for more information. */
  170. typedef struct RedisModuleKeyspaceSubscriber {
  171. /* The module subscribed to the event */
  172. RedisModule *module;
  173. /* Notification callback in the module*/
  174. RedisModuleNotificationFunc notify_callback;
  175. /* A bit mask of the events the module is interested in */
  176. int event_mask;
  177. /* Active flag set on entry, to avoid reentrant subscribers
  178. * calling themselves */
  179. int active;
  180. } RedisModuleKeyspaceSubscriber;
  181. /* The module keyspace notification subscribers list */
  182. static list *moduleKeyspaceSubscribers;
  183. /* Static client recycled for when we need to provide a context with a client
  184. * in a situation where there is no client to provide. This avoids allocating
  185. * a new client per round. For instance this is used in the keyspace
  186. * notifications, timers and cluster messages callbacks. */
  187. static client *moduleFreeContextReusedClient;
  188. /* Data structures related to the exported dictionary data structure. */
  189. typedef struct RedisModuleDict {
  190. rax *rax; /* The radix tree. */
  191. } RedisModuleDict;
  192. typedef struct RedisModuleDictIter {
  193. RedisModuleDict *dict;
  194. raxIterator ri;
  195. } RedisModuleDictIter;
  196. typedef struct RedisModuleCommandFilterCtx {
  197. RedisModuleString **argv;
  198. int argc;
  199. } RedisModuleCommandFilterCtx;
  200. typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
  201. typedef struct RedisModuleCommandFilter {
  202. /* The module that registered the filter */
  203. RedisModule *module;
  204. /* Filter callback function */
  205. RedisModuleCommandFilterFunc callback;
  206. /* REDISMODULE_CMDFILTER_* flags */
  207. int flags;
  208. } RedisModuleCommandFilter;
  209. /* Registered filters */
  210. static list *moduleCommandFilters;
  211. typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
  212. static struct RedisModuleForkInfo {
  213. RedisModuleForkDoneHandler done_handler;
  214. void* done_handler_user_data;
  215. } moduleForkInfo = {0};
  216. typedef struct RedisModuleServerInfoData {
  217. rax *rax; /* parsed info data. */
  218. } RedisModuleServerInfoData;
  219. /* Flags for moduleCreateArgvFromUserFormat(). */
  220. #define REDISMODULE_ARGV_REPLICATE (1<<0)
  221. #define REDISMODULE_ARGV_NO_AOF (1<<1)
  222. #define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
  223. /* Determine whether Redis should signalModifiedKey implicitly.
  224. * In case 'ctx' has no 'module' member (and therefore no module->options),
  225. * we assume default behavior, that is, Redis signals.
  226. * (see RM_GetThreadSafeContext) */
  227. #define SHOULD_SIGNAL_MODIFIED_KEYS(ctx) \
  228. ctx->module? !(ctx->module->options & REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED) : 1
  229. /* Server events hooks data structures and defines: this modules API
  230. * allow modules to subscribe to certain events in Redis, such as
  231. * the start and end of an RDB or AOF save, the change of role in replication,
  232. * and similar other events. */
  233. typedef struct RedisModuleEventListener {
  234. RedisModule *module;
  235. RedisModuleEvent event;
  236. RedisModuleEventCallback callback;
  237. } RedisModuleEventListener;
  238. list *RedisModule_EventListeners; /* Global list of all the active events. */
  239. unsigned long long ModulesInHooks = 0; /* Total number of modules in hooks
  240. callbacks right now. */
  241. /* Data structures related to the redis module users */
  242. /* This is the object returned by RM_CreateModuleUser(). The module API is
  243. * able to create users, set ACLs to such users, and later authenticate
  244. * clients using such newly created users. */
  245. typedef struct RedisModuleUser {
  246. user *user; /* Reference to the real redis user */
  247. } RedisModuleUser;
  248. /* --------------------------------------------------------------------------
  249. * Prototypes
  250. * -------------------------------------------------------------------------- */
  251. void RM_FreeCallReply(RedisModuleCallReply *reply);
  252. void RM_CloseKey(RedisModuleKey *key);
  253. void autoMemoryCollect(RedisModuleCtx *ctx);
  254. robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap);
  255. void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx);
  256. void RM_ZsetRangeStop(RedisModuleKey *kp);
  257. static void zsetKeyReset(RedisModuleKey *key);
  258. static void moduleInitKeyTypeSpecific(RedisModuleKey *key);
  259. void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
  260. void RM_FreeServerInfo(RedisModuleCtx *ctx, RedisModuleServerInfoData *data);
  261. /* --------------------------------------------------------------------------
  262. * ## Heap allocation raw functions
  263. *
  264. * Memory allocated with these functions are taken into account by Redis key
  265. * eviction algorithms and are reported in Redis memory usage information.
  266. * -------------------------------------------------------------------------- */
  267. /* Use like malloc(). Memory allocated with this function is reported in
  268. * Redis INFO memory, used for keys eviction according to maxmemory settings
  269. * and in general is taken into account as memory allocated by Redis.
  270. * You should avoid using malloc(). */
  271. void *RM_Alloc(size_t bytes) {
  272. return zmalloc(bytes);
  273. }
  274. /* Use like calloc(). Memory allocated with this function is reported in
  275. * Redis INFO memory, used for keys eviction according to maxmemory settings
  276. * and in general is taken into account as memory allocated by Redis.
  277. * You should avoid using calloc() directly. */
  278. void *RM_Calloc(size_t nmemb, size_t size) {
  279. return zcalloc(nmemb*size);
  280. }
  281. /* Use like realloc() for memory obtained with RedisModule_Alloc(). */
  282. void* RM_Realloc(void *ptr, size_t bytes) {
  283. return zrealloc(ptr,bytes);
  284. }
  285. /* Use like free() for memory obtained by RedisModule_Alloc() and
  286. * RedisModule_Realloc(). However you should never try to free with
  287. * RedisModule_Free() memory allocated with malloc() inside your module. */
  288. void RM_Free(void *ptr) {
  289. zfree(ptr);
  290. }
  291. /* Like strdup() but returns memory allocated with RedisModule_Alloc(). */
  292. char *RM_Strdup(const char *str) {
  293. return zstrdup(str);
  294. }
  295. /* --------------------------------------------------------------------------
  296. * Pool allocator
  297. * -------------------------------------------------------------------------- */
  298. /* Release the chain of blocks used for pool allocations. */
  299. void poolAllocRelease(RedisModuleCtx *ctx) {
  300. RedisModulePoolAllocBlock *head = ctx->pa_head, *next;
  301. while(head != NULL) {
  302. next = head->next;
  303. zfree(head);
  304. head = next;
  305. }
  306. ctx->pa_head = NULL;
  307. }
  308. /* Return heap allocated memory that will be freed automatically when the
  309. * module callback function returns. Mostly suitable for small allocations
  310. * that are short living and must be released when the callback returns
  311. * anyway. The returned memory is aligned to the architecture word size
  312. * if at least word size bytes are requested, otherwise it is just
  313. * aligned to the next power of two, so for example a 3 bytes request is
  314. * 4 bytes aligned while a 2 bytes request is 2 bytes aligned.
  315. *
  316. * There is no realloc style function since when this is needed to use the
  317. * pool allocator is not a good idea.
  318. *
  319. * The function returns NULL if `bytes` is 0. */
  320. void *RM_PoolAlloc(RedisModuleCtx *ctx, size_t bytes) {
  321. if (bytes == 0) return NULL;
  322. RedisModulePoolAllocBlock *b = ctx->pa_head;
  323. size_t left = b ? b->size - b->used : 0;
  324. /* Fix alignment. */
  325. if (left >= bytes) {
  326. size_t alignment = REDISMODULE_POOL_ALLOC_ALIGN;
  327. while (bytes < alignment && alignment/2 >= bytes) alignment /= 2;
  328. if (b->used % alignment)
  329. b->used += alignment - (b->used % alignment);
  330. left = (b->used > b->size) ? 0 : b->size - b->used;
  331. }
  332. /* Create a new block if needed. */
  333. if (left < bytes) {
  334. size_t blocksize = REDISMODULE_POOL_ALLOC_MIN_SIZE;
  335. if (blocksize < bytes) blocksize = bytes;
  336. b = zmalloc(sizeof(*b) + blocksize);
  337. b->size = blocksize;
  338. b->used = 0;
  339. b->next = ctx->pa_head;
  340. ctx->pa_head = b;
  341. }
  342. char *retval = b->memory + b->used;
  343. b->used += bytes;
  344. return retval;
  345. }
  346. /* --------------------------------------------------------------------------
  347. * Helpers for modules API implementation
  348. * -------------------------------------------------------------------------- */
  349. /* Create an empty key of the specified type. `key` must point to a key object
  350. * opened for writing where the `.value` member is set to NULL because the
  351. * key was found to be non existing.
  352. *
  353. * On success REDISMODULE_OK is returned and the key is populated with
  354. * the value of the specified type. The function fails and returns
  355. * REDISMODULE_ERR if:
  356. *
  357. * 1. The key is not open for writing.
  358. * 2. The key is not empty.
  359. * 3. The specified type is unknown.
  360. */
  361. int moduleCreateEmptyKey(RedisModuleKey *key, int type) {
  362. robj *obj;
  363. /* The key must be open for writing and non existing to proceed. */
  364. if (!(key->mode & REDISMODULE_WRITE) || key->value)
  365. return REDISMODULE_ERR;
  366. switch(type) {
  367. case REDISMODULE_KEYTYPE_LIST:
  368. obj = createQuicklistObject();
  369. quicklistSetOptions(obj->ptr, server.list_max_ziplist_size,
  370. server.list_compress_depth);
  371. break;
  372. case REDISMODULE_KEYTYPE_ZSET:
  373. obj = createZsetZiplistObject();
  374. break;
  375. case REDISMODULE_KEYTYPE_HASH:
  376. obj = createHashObject();
  377. break;
  378. case REDISMODULE_KEYTYPE_STREAM:
  379. obj = createStreamObject();
  380. break;
  381. default: return REDISMODULE_ERR;
  382. }
  383. dbAdd(key->db,key->key,obj);
  384. key->value = obj;
  385. moduleInitKeyTypeSpecific(key);
  386. return REDISMODULE_OK;
  387. }
  388. /* This function is called in low-level API implementation functions in order
  389. * to check if the value associated with the key remained empty after an
  390. * operation that removed elements from an aggregate data type.
  391. *
  392. * If this happens, the key is deleted from the DB and the key object state
  393. * is set to the right one in order to be targeted again by write operations
  394. * possibly recreating the key if needed.
  395. *
  396. * The function returns 1 if the key value object is found empty and is
  397. * deleted, otherwise 0 is returned. */
  398. int moduleDelKeyIfEmpty(RedisModuleKey *key) {
  399. if (!(key->mode & REDISMODULE_WRITE) || key->value == NULL) return 0;
  400. int isempty;
  401. robj *o = key->value;
  402. switch(o->type) {
  403. case OBJ_LIST: isempty = listTypeLength(o) == 0; break;
  404. case OBJ_SET: isempty = setTypeSize(o) == 0; break;
  405. case OBJ_ZSET: isempty = zsetLength(o) == 0; break;
  406. case OBJ_HASH: isempty = hashTypeLength(o) == 0; break;
  407. case OBJ_STREAM: isempty = streamLength(o) == 0; break;
  408. default: isempty = 0;
  409. }
  410. if (isempty) {
  411. dbDelete(key->db,key->key);
  412. key->value = NULL;
  413. return 1;
  414. } else {
  415. return 0;
  416. }
  417. }
  418. /* --------------------------------------------------------------------------
  419. * Service API exported to modules
  420. *
  421. * Note that all the exported APIs are called RM_<funcname> in the core
  422. * and RedisModule_<funcname> in the module side (defined as function
  423. * pointers in redismodule.h). In this way the dynamic linker does not
  424. * mess with our global function pointers, overriding it with the symbols
  425. * defined in the main executable having the same names.
  426. * -------------------------------------------------------------------------- */
  427. int RM_GetApi(const char *funcname, void **targetPtrPtr) {
  428. /* Lookup the requested module API and store the function pointer into the
  429. * target pointer. The function returns REDISMODULE_ERR if there is no such
  430. * named API, otherwise REDISMODULE_OK.
  431. *
  432. * This function is not meant to be used by modules developer, it is only
  433. * used implicitly by including redismodule.h. */
  434. dictEntry *he = dictFind(server.moduleapi, funcname);
  435. if (!he) return REDISMODULE_ERR;
  436. *targetPtrPtr = dictGetVal(he);
  437. return REDISMODULE_OK;
  438. }
  439. /* Helper function for when a command callback is called, in order to handle
  440. * details needed to correctly replicate commands. */
  441. void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
  442. client *c = ctx->client;
  443. /* We don't need to do anything here if the context was never used
  444. * in order to propagate commands. */
  445. if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
  446. /* We don't need to do anything here if the server isn't inside
  447. * a transaction. */
  448. if (!server.propagate_in_transaction) return;
  449. /* If this command is executed from with Lua or MULTI/EXEC we do not
  450. * need to propagate EXEC */
  451. if (server.in_eval || server.in_exec) return;
  452. /* Handle the replication of the final EXEC, since whatever a command
  453. * emits is always wrapped around MULTI/EXEC. */
  454. alsoPropagate(server.execCommand,c->db->id,&shared.exec,1,
  455. PROPAGATE_AOF|PROPAGATE_REPL);
  456. afterPropagateExec();
  457. /* If this is not a module command context (but is instead a simple
  458. * callback context), we have to handle directly the "also propagate"
  459. * array and emit it. In a module command call this will be handled
  460. * directly by call(). */
  461. if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
  462. server.also_propagate.numops)
  463. {
  464. for (int j = 0; j < server.also_propagate.numops; j++) {
  465. redisOp *rop = &server.also_propagate.ops[j];
  466. int target = rop->target;
  467. if (target)
  468. propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
  469. }
  470. redisOpArrayFree(&server.also_propagate);
  471. /* Restore the previous oparray in case of nexted use of the API. */
  472. server.also_propagate = ctx->saved_oparray;
  473. /* We're done with saved_oparray, let's invalidate it. */
  474. redisOpArrayInit(&ctx->saved_oparray);
  475. }
  476. }

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载