PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/multi.c

http://github.com/antirez/redis
C | 374 lines | 234 code | 41 blank | 99 comment | 46 complexity | 8d6b2ba9cf2d8a3ae37e60ec57482f95 MD5 | raw file
Possible License(s): BSD-3-Clause, BSD-2-Clause
  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. c->mstate.cmd_flags = 0;
  36. }
  37. /* Release all the resources associated with MULTI/EXEC state */
  38. void freeClientMultiState(client *c) {
  39. int j;
  40. for (j = 0; j < c->mstate.count; j++) {
  41. int i;
  42. multiCmd *mc = c->mstate.commands+j;
  43. for (i = 0; i < mc->argc; i++)
  44. decrRefCount(mc->argv[i]);
  45. zfree(mc->argv);
  46. }
  47. zfree(c->mstate.commands);
  48. }
  49. /* Add a new command into the MULTI commands queue */
  50. void queueMultiCommand(client *c) {
  51. multiCmd *mc;
  52. int j;
  53. c->mstate.commands = zrealloc(c->mstate.commands,
  54. sizeof(multiCmd)*(c->mstate.count+1));
  55. mc = c->mstate.commands+c->mstate.count;
  56. mc->cmd = c->cmd;
  57. mc->argc = c->argc;
  58. mc->argv = zmalloc(sizeof(robj*)*c->argc);
  59. memcpy(mc->argv,c->argv,sizeof(robj*)*c->argc);
  60. for (j = 0; j < c->argc; j++)
  61. incrRefCount(mc->argv[j]);
  62. c->mstate.count++;
  63. c->mstate.cmd_flags |= c->cmd->flags;
  64. }
  65. void discardTransaction(client *c) {
  66. freeClientMultiState(c);
  67. initClientMultiState(c);
  68. c->flags &= ~(CLIENT_MULTI|CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC);
  69. unwatchAllKeys(c);
  70. }
  71. /* Flag the transacation as DIRTY_EXEC so that EXEC will fail.
  72. * Should be called every time there is an error while queueing a command. */
  73. void flagTransaction(client *c) {
  74. if (c->flags & CLIENT_MULTI)
  75. c->flags |= CLIENT_DIRTY_EXEC;
  76. }
  77. void multiCommand(client *c) {
  78. if (c->flags & CLIENT_MULTI) {
  79. addReplyError(c,"MULTI calls can not be nested");
  80. return;
  81. }
  82. c->flags |= CLIENT_MULTI;
  83. addReply(c,shared.ok);
  84. }
  85. void discardCommand(client *c) {
  86. if (!(c->flags & CLIENT_MULTI)) {
  87. addReplyError(c,"DISCARD without MULTI");
  88. return;
  89. }
  90. discardTransaction(c);
  91. addReply(c,shared.ok);
  92. }
  93. /* Send a MULTI command to all the slaves and AOF file. Check the execCommand
  94. * implementation for more information. */
  95. void execCommandPropagateMulti(client *c) {
  96. propagate(server.multiCommand,c->db->id,&shared.multi,1,
  97. PROPAGATE_AOF|PROPAGATE_REPL);
  98. }
  99. void execCommandPropagateExec(client *c) {
  100. propagate(server.execCommand,c->db->id,&shared.exec,1,
  101. PROPAGATE_AOF|PROPAGATE_REPL);
  102. }
  103. void execCommand(client *c) {
  104. int j;
  105. robj **orig_argv;
  106. int orig_argc;
  107. struct redisCommand *orig_cmd;
  108. int must_propagate = 0; /* Need to propagate MULTI/EXEC to AOF / slaves? */
  109. int was_master = server.masterhost == NULL;
  110. if (!(c->flags & CLIENT_MULTI)) {
  111. addReplyError(c,"EXEC without MULTI");
  112. return;
  113. }
  114. /* Check if we need to abort the EXEC because:
  115. * 1) Some WATCHed key was touched.
  116. * 2) There was a previous error while queueing commands.
  117. * A failed EXEC in the first case returns a multi bulk nil object
  118. * (technically it is not an error but a special behavior), while
  119. * in the second an EXECABORT error is returned. */
  120. if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
  121. addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
  122. shared.nullarray[c->resp]);
  123. discardTransaction(c);
  124. goto handle_monitor;
  125. }
  126. /* If there are write commands inside the transaction, and this is a read
  127. * only slave, we want to send an error. This happens when the transaction
  128. * was initiated when the instance was a master or a writable replica and
  129. * then the configuration changed (for example instance was turned into
  130. * a replica). */
  131. if (!server.loading && server.masterhost && server.repl_slave_ro &&
  132. !(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
  133. {
  134. addReplyError(c,
  135. "Transaction contains write commands but instance "
  136. "is now a read-only replica. EXEC aborted.");
  137. discardTransaction(c);
  138. goto handle_monitor;
  139. }
  140. /* Exec all the queued commands */
  141. unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
  142. orig_argv = c->argv;
  143. orig_argc = c->argc;
  144. orig_cmd = c->cmd;
  145. addReplyArrayLen(c,c->mstate.count);
  146. for (j = 0; j < c->mstate.count; j++) {
  147. c->argc = c->mstate.commands[j].argc;
  148. c->argv = c->mstate.commands[j].argv;
  149. c->cmd = c->mstate.commands[j].cmd;
  150. /* Propagate a MULTI request once we encounter the first command which
  151. * is not readonly nor an administrative one.
  152. * This way we'll deliver the MULTI/..../EXEC block as a whole and
  153. * both the AOF and the replication link will have the same consistency
  154. * and atomicity guarantees. */
  155. if (!must_propagate &&
  156. !server.loading &&
  157. !(c->cmd->flags & (CMD_READONLY|CMD_ADMIN)))
  158. {
  159. execCommandPropagateMulti(c);
  160. must_propagate = 1;
  161. }
  162. int acl_keypos;
  163. int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
  164. if (acl_retval != ACL_OK) {
  165. addACLLogEntry(c,acl_retval,acl_keypos,NULL);
  166. addReplyErrorFormat(c,
  167. "-NOPERM ACLs rules changed between the moment the "
  168. "transaction was accumulated and the EXEC call. "
  169. "This command is no longer allowed for the "
  170. "following reason: %s",
  171. (acl_retval == ACL_DENIED_CMD) ?
  172. "no permission to execute the command or subcommand" :
  173. "no permission to touch the specified keys");
  174. } else {
  175. call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
  176. }
  177. /* Commands may alter argc/argv, restore mstate. */
  178. c->mstate.commands[j].argc = c->argc;
  179. c->mstate.commands[j].argv = c->argv;
  180. c->mstate.commands[j].cmd = c->cmd;
  181. }
  182. c->argv = orig_argv;
  183. c->argc = orig_argc;
  184. c->cmd = orig_cmd;
  185. discardTransaction(c);
  186. /* Make sure the EXEC command will be propagated as well if MULTI
  187. * was already propagated. */
  188. if (must_propagate) {
  189. int is_master = server.masterhost == NULL;
  190. server.dirty++;
  191. /* If inside the MULTI/EXEC block this instance was suddenly
  192. * switched from master to slave (using the SLAVEOF command), the
  193. * initial MULTI was propagated into the replication backlog, but the
  194. * rest was not. We need to make sure to at least terminate the
  195. * backlog with the final EXEC. */
  196. if (server.repl_backlog && was_master && !is_master) {
  197. char *execcmd = "*1\r\n$4\r\nEXEC\r\n";
  198. feedReplicationBacklog(execcmd,strlen(execcmd));
  199. }
  200. }
  201. handle_monitor:
  202. /* Send EXEC to clients waiting data from MONITOR. We do it here
  203. * since the natural order of commands execution is actually:
  204. * MUTLI, EXEC, ... commands inside transaction ...
  205. * Instead EXEC is flagged as CMD_SKIP_MONITOR in the command
  206. * table, and we do it here with correct ordering. */
  207. if (listLength(server.monitors) && !server.loading)
  208. replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
  209. }
  210. /* ===================== WATCH (CAS alike for MULTI/EXEC) ===================
  211. *
  212. * The implementation uses a per-DB hash table mapping keys to list of clients
  213. * WATCHing those keys, so that given a key that is going to be modified
  214. * we can mark all the associated clients as dirty.
  215. *
  216. * Also every client contains a list of WATCHed keys so that's possible to
  217. * un-watch such keys when the client is freed or when UNWATCH is called. */
  218. /* In the client->watched_keys list we need to use watchedKey structures
  219. * as in order to identify a key in Redis we need both the key name and the
  220. * DB */
  221. typedef struct watchedKey {
  222. robj *key;
  223. redisDb *db;
  224. } watchedKey;
  225. /* Watch for the specified key */
  226. void watchForKey(client *c, robj *key) {
  227. list *clients = NULL;
  228. listIter li;
  229. listNode *ln;
  230. watchedKey *wk;
  231. /* Check if we are already watching for this key */
  232. listRewind(c->watched_keys,&li);
  233. while((ln = listNext(&li))) {
  234. wk = listNodeValue(ln);
  235. if (wk->db == c->db && equalStringObjects(key,wk->key))
  236. return; /* Key already watched */
  237. }
  238. /* This key is not already watched in this DB. Let's add it */
  239. clients = dictFetchValue(c->db->watched_keys,key);
  240. if (!clients) {
  241. clients = listCreate();
  242. dictAdd(c->db->watched_keys,key,clients);
  243. incrRefCount(key);
  244. }
  245. listAddNodeTail(clients,c);
  246. /* Add the new key to the list of keys watched by this client */
  247. wk = zmalloc(sizeof(*wk));
  248. wk->key = key;
  249. wk->db = c->db;
  250. incrRefCount(key);
  251. listAddNodeTail(c->watched_keys,wk);
  252. }
  253. /* Unwatch all the keys watched by this client. To clean the EXEC dirty
  254. * flag is up to the caller. */
  255. void unwatchAllKeys(client *c) {
  256. listIter li;
  257. listNode *ln;
  258. if (listLength(c->watched_keys) == 0) return;
  259. listRewind(c->watched_keys,&li);
  260. while((ln = listNext(&li))) {
  261. list *clients;
  262. watchedKey *wk;
  263. /* Lookup the watched key -> clients list and remove the client
  264. * from the list */
  265. wk = listNodeValue(ln);
  266. clients = dictFetchValue(wk->db->watched_keys, wk->key);
  267. serverAssertWithInfo(c,NULL,clients != NULL);
  268. listDelNode(clients,listSearchKey(clients,c));
  269. /* Kill the entry at all if this was the only client */
  270. if (listLength(clients) == 0)
  271. dictDelete(wk->db->watched_keys, wk->key);
  272. /* Remove this watched key from the client->watched list */
  273. listDelNode(c->watched_keys,ln);
  274. decrRefCount(wk->key);
  275. zfree(wk);
  276. }
  277. }
  278. /* "Touch" a key, so that if this key is being WATCHed by some client the
  279. * next EXEC will fail. */
  280. void touchWatchedKey(redisDb *db, robj *key) {
  281. list *clients;
  282. listIter li;
  283. listNode *ln;
  284. if (dictSize(db->watched_keys) == 0) return;
  285. clients = dictFetchValue(db->watched_keys, key);
  286. if (!clients) return;
  287. /* Mark all the clients watching this key as CLIENT_DIRTY_CAS */
  288. /* Check if we are already watching for this key */
  289. listRewind(clients,&li);
  290. while((ln = listNext(&li))) {
  291. client *c = listNodeValue(ln);
  292. c->flags |= CLIENT_DIRTY_CAS;
  293. }
  294. }
  295. /* On FLUSHDB or FLUSHALL all the watched keys that are present before the
  296. * flush but will be deleted as effect of the flushing operation should
  297. * be touched. "dbid" is the DB that's getting the flush. -1 if it is
  298. * a FLUSHALL operation (all the DBs flushed). */
  299. void touchWatchedKeysOnFlush(int dbid) {
  300. listIter li1, li2;
  301. listNode *ln;
  302. /* For every client, check all the waited keys */
  303. listRewind(server.clients,&li1);
  304. while((ln = listNext(&li1))) {
  305. client *c = listNodeValue(ln);
  306. listRewind(c->watched_keys,&li2);
  307. while((ln = listNext(&li2))) {
  308. watchedKey *wk = listNodeValue(ln);
  309. /* For every watched key matching the specified DB, if the
  310. * key exists, mark the client as dirty, as the key will be
  311. * removed. */
  312. if (dbid == -1 || wk->db->id == dbid) {
  313. if (dictFind(wk->db->dict, wk->key->ptr) != NULL)
  314. c->flags |= CLIENT_DIRTY_CAS;
  315. }
  316. }
  317. }
  318. }
  319. void watchCommand(client *c) {
  320. int j;
  321. if (c->flags & CLIENT_MULTI) {
  322. addReplyError(c,"WATCH inside MULTI is not allowed");
  323. return;
  324. }
  325. for (j = 1; j < c->argc; j++)
  326. watchForKey(c,c->argv[j]);
  327. addReply(c,shared.ok);
  328. }
  329. void unwatchCommand(client *c) {
  330. unwatchAllKeys(c);
  331. c->flags &= (~CLIENT_DIRTY_CAS);
  332. addReply(c,shared.ok);
  333. }