PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/multi.c

https://gitlab.com/unofficial-mirrors/redis
C | 338 lines | 204 code | 40 blank | 94 comment | 39 complexity | f46ebb3c20e1255910ce7cb2cd984feb MD5 | raw file
  1. /*
  2. * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * * Redistributions of source code must retain the above copyright notice,
  9. * this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. * * Neither the name of Redis nor the names of its contributors may be used
  14. * to endorse or promote products derived from this software without
  15. * specific prior written permission.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. * POSSIBILITY OF SUCH DAMAGE.
  28. */
  29. #include "server.h"
  30. /* ================================ MULTI/EXEC ============================== */
  31. /* Client state initialization for MULTI/EXEC */
  32. void initClientMultiState(client *c) {
  33. c->mstate.commands = NULL;
  34. c->mstate.count = 0;
  35. }
  36. /* Release all the resources associated with MULTI/EXEC state */
  37. void freeClientMultiState(client *c) {
  38. int j;
  39. for (j = 0; j < c->mstate.count; j++) {
  40. int i;
  41. multiCmd *mc = c->mstate.commands+j;
  42. for (i = 0; i < mc->argc; i++)
  43. decrRefCount(mc->argv[i]);
  44. zfree(mc->argv);
  45. }
  46. zfree(c->mstate.commands);
  47. }
  48. /* Add a new command into the MULTI commands queue */
  49. void queueMultiCommand(client *c) {
  50. multiCmd *mc;
  51. int j;
  52. c->mstate.commands = zrealloc(c->mstate.commands,
  53. sizeof(multiCmd)*(c->mstate.count+1));
  54. mc = c->mstate.commands+c->mstate.count;
  55. mc->cmd = c->cmd;
  56. mc->argc = c->argc;
  57. mc->argv = zmalloc(sizeof(robj*)*c->argc);
  58. memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
  59. for (j = 0; j < c->argc; j++)
  60. incrRefCount(mc->argv[j]);
  61. c->mstate.count++;
  62. }
  63. void discardTransaction(client *c) {
  64. freeClientMultiState(c);
  65. initClientMultiState(c);
  66. c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC);
  67. unwatchAllKeys(c);
  68. }
  69. /* Flag the transacation as DIRTY_EXEC so that EXEC will fail.
  70. * Should be called every time there is an error while queueing a command. */
  71. void flagTransaction(client *c) {
  72. if (c->flags & CLIENT_MULTI)
  73. c->flags |= CLIENT_DIRTY_EXEC;
  74. }
  75. void multiCommand(client *c) {
  76. if (c->flags & CLIENT_MULTI) {
  77. addReplyError(c,"MULTI calls can not be nested");
  78. return;
  79. }
  80. c->flags |= CLIENT_MULTI;
  81. addReply(c,shared.ok);
  82. }
  83. void discardCommand(client *c) {
  84. if (!(c->flags & CLIENT_MULTI)) {
  85. addReplyError(c,"DISCARD without MULTI");
  86. return;
  87. }
  88. discardTransaction(c);
  89. addReply(c,shared.ok);
  90. }
  91. /* Send a MULTI command to all the slaves and AOF file. Check the execCommand
  92. * implementation for more information. */
  93. void execCommandPropagateMulti(client *c) {
  94. robj *multistring = createStringObject("MULTI",5);
  95. propagate(server.multiCommand,c->db->id,&multistring,1,
  96. PROPAGATE_AOF|PROPAGATE_REPL);
  97. decrRefCount(multistring);
  98. }
  99. void execCommand(client *c) {
  100. int j;
  101. robj **orig_argv;
  102. int orig_argc;
  103. struct redisCommand *orig_cmd;
  104. int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
  105. int was_master = server.masterhost == NULL;
  106. if (!(c->flags & CLIENT_MULTI)) {
  107. addReplyError(c,"EXEC without MULTI");
  108. return;
  109. }
  110. /* Check if we need to abort the EXEC because:
  111. * 1) Some WATCHed key was touched.
  112. * 2) There was a previous error while queueing commands.
  113. * A failed EXEC in the first case returns a multi bulk nil object
  114. * (technically it is not an error but a special behavior), while
  115. * in the second an EXECABORT error is returned. */
  116. if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
  117. addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
  118. shared.nullmultibulk);
  119. discardTransaction(c);
  120. goto handle_monitor;
  121. }
  122. /* Exec all the queued commands */
  123. unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
  124. orig_argv = c->argv;
  125. orig_argc = c->argc;
  126. orig_cmd = c->cmd;
  127. addReplyMultiBulkLen(c,c->mstate.count);
  128. for (j = 0; j < c->mstate.count; j++) {
  129. c->argc = c->mstate.commands[j].argc;
  130. c->argv = c->mstate.commands[j].argv;
  131. c->cmd = c->mstate.commands[j].cmd;
  132. /* Propagate a MULTI request once we encounter the first command which
  133. * is not readonly nor an administrative one.
  134. * This way we'll deliver the MULTI/..../EXEC block as a whole and
  135. * both the AOF and the replication link will have the same consistency
  136. * and atomicity guarantees. */
  137. if (!must_propagate && !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN))) {
  138. execCommandPropagateMulti(c);
  139. must_propagate = 1;
  140. }
  141. call(c,CMD_CALL_FULL);
  142. /* Commands may alter argc/argv, restore mstate. */
  143. c->mstate.commands[j].argc = c->argc;
  144. c->mstate.commands[j].argv = c->argv;
  145. c->mstate.commands[j].cmd = c->cmd;
  146. }
  147. c->argv = orig_argv;
  148. c->argc = orig_argc;
  149. c->cmd = orig_cmd;
  150. discardTransaction(c);
  151. /* Make sure the EXEC command will be propagated as well if MULTI
  152. * was already propagated. */
  153. if (must_propagate) {
  154. int is_master = server.masterhost == NULL;
  155. server.dirty++;
  156. /* If inside the MULTI/EXEC block this instance was suddenly
  157. * switched from master to slave (using the SLAVEOF command), the
  158. * initial MULTI was propagated into the replication backlog, but the
  159. * rest was not. We need to make sure to at least terminate the
  160. * backlog with the final EXEC. */
  161. if (server.repl_backlog && was_master && !is_master) {
  162. char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
  163. feedReplicationBacklog(execcmd,strlen(execcmd));
  164. }
  165. }
  166. handle_monitor:
  167. /* Send EXEC to clients waiting data from MONITOR. We do it here
  168. * since the natural order of commands execution is actually:
  169. * MUTLI, EXEC, ... commands inside transaction ...
  170. * Instead EXEC is flagged as CMD_SKIP_MONITOR in the command
  171. * table, and we do it here with correct ordering. */
  172. if (listLength(server.monitors) && !server.loading)
  173. replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
  174. }
  175. /* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
  176. *
  177. * The implementation uses a per-DB hash table mapping keys to list of clients
  178. * WATCHing those keys, so that given a key that is going to be modified
  179. * we can mark all the associated clients as dirty.
  180. *
  181. * Also every client contains a list of WATCHed keys so that's possible to
  182. * un-watch such keys when the client is freed or when UNWATCH is called. */
  183. /* In the client->watched_keys list we need to use watchedKey structures
  184. * as in order to identify a key in Redis we need both the key name and the
  185. * DB */
  186. typedef struct watchedKey {
  187. robj *key;
  188. redisDb *db;
  189. } watchedKey;
  190. /* Watch for the specified key */
  191. void watchForKey(client *c, robj *key) {
  192. list *clients = NULL;
  193. listIter li;
  194. listNode *ln;
  195. watchedKey *wk;
  196. /* Check if we are already watching for this key */
  197. listRewind(c->watched_keys,&li);
  198. while((ln = listNext(&li))) {
  199. wk = listNodeValue(ln);
  200. if (wk->db == c->db && equalStringObjects(key,wk->key))
  201. return; /* Key already watched */
  202. }
  203. /* This key is not already watched in this DB. Let's add it */
  204. clients = dictFetchValue(c->db->watched_keys,key);
  205. if (!clients) {
  206. clients = listCreate();
  207. dictAdd(c->db->watched_keys,key,clients);
  208. incrRefCount(key);
  209. }
  210. listAddNodeTail(clients,c);
  211. /* Add the new key to the list of keys watched by this client */
  212. wk = zmalloc(sizeof(*wk));
  213. wk->key = key;
  214. wk->db = c->db;
  215. incrRefCount(key);
  216. listAddNodeTail(c->watched_keys,wk);
  217. }
  218. /* Unwatch all the keys watched by this client. To clean the EXEC dirty
  219. * flag is up to the caller. */
  220. void unwatchAllKeys(client *c) {
  221. listIter li;
  222. listNode *ln;
  223. if (listLength(c->watched_keys) == 0) return;
  224. listRewind(c->watched_keys,&li);
  225. while((ln = listNext(&li))) {
  226. list *clients;
  227. watchedKey *wk;
  228. /* Lookup the watched key -> clients list and remove the client
  229. * from the list */
  230. wk = listNodeValue(ln);
  231. clients = dictFetchValue(wk->db->watched_keys, wk->key);
  232. serverAssertWithInfo(c,NULL,clients != NULL);
  233. listDelNode(clients,listSearchKey(clients,c));
  234. /* Kill the entry at all if this was the only client */
  235. if (listLength(clients) == 0)
  236. dictDelete(wk->db->watched_keys, wk->key);
  237. /* Remove this watched key from the client->watched list */
  238. listDelNode(c->watched_keys,ln);
  239. decrRefCount(wk->key);
  240. zfree(wk);
  241. }
  242. }
  243. /* "Touch" a key, so that if this key is being WATCHed by some client the
  244. * next EXEC will fail. */
  245. void touchWatchedKey(redisDb *db, robj *key) {
  246. list *clients;
  247. listIter li;
  248. listNode *ln;
  249. if (dictSize(db->watched_keys) == 0) return;
  250. clients = dictFetchValue(db->watched_keys, key);
  251. if (!clients) return;
  252. /* Mark all the clients watching this key as CLIENT_DIRTY_CAS */
  253. /* Check if we are already watching for this key */
  254. listRewind(clients,&li);
  255. while((ln = listNext(&li))) {
  256. client *c = listNodeValue(ln);
  257. c->flags |= CLIENT_DIRTY_CAS;
  258. }
  259. }
  260. /* On FLUSHDB or FLUSHALL all the watched keys that are present before the
  261. * flush but will be deleted as effect of the flushing operation should
  262. * be touched. "dbid" is the DB that's getting the flush. -1 if it is
  263. * a FLUSHALL operation (all the DBs flushed). */
  264. void touchWatchedKeysOnFlush(int dbid) {
  265. listIter li1, li2;
  266. listNode *ln;
  267. /* For every client, check all the waited keys */
  268. listRewind(server.clients,&li1);
  269. while((ln = listNext(&li1))) {
  270. client *c = listNodeValue(ln);
  271. listRewind(c->watched_keys,&li2);
  272. while((ln = listNext(&li2))) {
  273. watchedKey *wk = listNodeValue(ln);
  274. /* For every watched key matching the specified DB, if the
  275. * key exists, mark the client as dirty, as the key will be
  276. * removed. */
  277. if (dbid == -1 || wk->db->id == dbid) {
  278. if (dictFind(wk->db->dict, wk->key->ptr) != NULL)
  279. c->flags |= CLIENT_DIRTY_CAS;
  280. }
  281. }
  282. }
  283. }
  284. void watchCommand(client *c) {
  285. int j;
  286. if (c->flags & CLIENT_MULTI) {
  287. addReplyError(c,"WATCH inside MULTI is not allowed");
  288. return;
  289. }
  290. for (j = 1; j < c->argc; j++)
  291. watchForKey(c,c->argv[j]);
  292. addReply(c,shared.ok);
  293. }
  294. void unwatchCommand(client *c) {
  295. unwatchAllKeys(c);
  296. c->flags &= (~CLIENT_DIRTY_CAS);
  297. addReply(c,shared.ok);
  298. }