1/* Copyright (c) 2009-2020, Salvatore Sanfilippo <antirez at gmail dot com>
2 * All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * * Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of Redis nor the names of its contributors may be used
13 * to endorse or promote products derived from this software without
14 * specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "server.h"
30#include "cluster.h"
31
32/* ========================== Clients timeouts ============================= */
33
34/* Check if this blocked client timedout (does nothing if the client is
35 * not blocked right now). If so send a reply, unblock it, and return 1.
36 * Otherwise 0 is returned and no operation is performed. */
37int checkBlockedClientTimeout(client *c, mstime_t now) {
38 if (c->flags & CLIENT_BLOCKED &&
39 c->bpop.timeout != 0
40 && c->bpop.timeout < now)
41 {
42 /* Handle blocking operation specific timeout. */
43 replyToBlockedClientTimedOut(c);
44 unblockClient(c);
45 return 1;
46 } else {
47 return 0;
48 }
49}
50
51/* Check for timeouts. Returns non-zero if the client was terminated.
52 * The function gets the current time in milliseconds as argument since
53 * it gets called multiple times in a loop, so calling gettimeofday() for
54 * each iteration would be costly without any actual gain. */
55int clientsCronHandleTimeout(client *c, mstime_t now_ms) {
56 time_t now = now_ms/1000;
57
58 if (server.maxidletime &&
59 /* This handles the idle clients connection timeout if set. */
60 !(c->flags & CLIENT_SLAVE) && /* No timeout for slaves and monitors */
61 !mustObeyClient(c) && /* No timeout for masters and AOF */
62 !(c->flags & CLIENT_BLOCKED) && /* No timeout for BLPOP */
63 !(c->flags & CLIENT_PUBSUB) && /* No timeout for Pub/Sub clients */
64 (now - c->lastinteraction > server.maxidletime))
65 {
66 serverLog(LL_VERBOSE,"Closing idle client");
67 freeClient(c);
68 return 1;
69 } else if (c->flags & CLIENT_BLOCKED) {
70 /* Cluster: handle unblock & redirect of clients blocked
71 * into keys no longer served by this server. */
72 if (server.cluster_enabled) {
73 if (clusterRedirectBlockedClientIfNeeded(c))
74 unblockClient(c);
75 }
76 }
77 return 0;
78}
79
80/* For blocked clients timeouts we populate a radix tree of 128 bit keys
81 * composed as such:
82 *
83 * [8 byte big endian expire time]+[8 byte client ID]
84 *
85 * We don't do any cleanup in the Radix tree: when we run the clients that
86 * reached the timeout already, if they are no longer existing or no longer
87 * blocked with such timeout, we just go forward.
88 *
89 * Every time a client blocks with a timeout, we add the client in
90 * the tree. In beforeSleep() we call handleBlockedClientsTimeout() to run
91 * the tree and unblock the clients. */
92
93#define CLIENT_ST_KEYLEN 16 /* 8 bytes mstime + 8 bytes client ID. */
94
95/* Given client ID and timeout, write the resulting radix tree key in buf. */
96void encodeTimeoutKey(unsigned char *buf, uint64_t timeout, client *c) {
97 timeout = htonu64(timeout);
98 memcpy(buf,&timeout,sizeof(timeout));
99 memcpy(buf+8,&c,sizeof(c));
100 if (sizeof(c) == 4) memset(buf+12,0,4); /* Zero padding for 32bit target. */
101}
102
103/* Given a key encoded with encodeTimeoutKey(), resolve the fields and write
104 * the timeout into *toptr and the client pointer into *cptr. */
105void decodeTimeoutKey(unsigned char *buf, uint64_t *toptr, client **cptr) {
106 memcpy(toptr,buf,sizeof(*toptr));
107 *toptr = ntohu64(*toptr);
108 memcpy(cptr,buf+8,sizeof(*cptr));
109}
110
111/* Add the specified client id / timeout as a key in the radix tree we use
112 * to handle blocked clients timeouts. The client is not added to the list
113 * if its timeout is zero (block forever). */
114void addClientToTimeoutTable(client *c) {
115 if (c->bpop.timeout == 0) return;
116 uint64_t timeout = c->bpop.timeout;
117 unsigned char buf[CLIENT_ST_KEYLEN];
118 encodeTimeoutKey(buf,timeout,c);
119 if (raxTryInsert(server.clients_timeout_table,buf,sizeof(buf),NULL,NULL))
120 c->flags |= CLIENT_IN_TO_TABLE;
121}
122
123/* Remove the client from the table when it is unblocked for reasons
124 * different than timing out. */
125void removeClientFromTimeoutTable(client *c) {
126 if (!(c->flags & CLIENT_IN_TO_TABLE)) return;
127 c->flags &= ~CLIENT_IN_TO_TABLE;
128 uint64_t timeout = c->bpop.timeout;
129 unsigned char buf[CLIENT_ST_KEYLEN];
130 encodeTimeoutKey(buf,timeout,c);
131 raxRemove(server.clients_timeout_table,buf,sizeof(buf),NULL);
132}
133
134/* This function is called in beforeSleep() in order to unblock clients
135 * that are waiting in blocking operations with a timeout set. */
136void handleBlockedClientsTimeout(void) {
137 if (raxSize(server.clients_timeout_table) == 0) return;
138 uint64_t now = mstime();
139 raxIterator ri;
140 raxStart(&ri,server.clients_timeout_table);
141 raxSeek(&ri,"^",NULL,0);
142
143 while(raxNext(&ri)) {
144 uint64_t timeout;
145 client *c;
146 decodeTimeoutKey(ri.key,&timeout,&c);
147 if (timeout >= now) break; /* All the timeouts are in the future. */
148 c->flags &= ~CLIENT_IN_TO_TABLE;
149 checkBlockedClientTimeout(c,now);
150 raxRemove(server.clients_timeout_table,ri.key,ri.key_len,NULL);
151 raxSeek(&ri,"^",NULL,0);
152 }
153 raxStop(&ri);
154}
155
156/* Get a timeout value from an object and store it into 'timeout'.
157 * The final timeout is always stored as milliseconds as a time where the
158 * timeout will expire, however the parsing is performed according to
159 * the 'unit' that can be seconds or milliseconds.
160 *
161 * Note that if the timeout is zero (usually from the point of view of
162 * commands API this means no timeout) the value stored into 'timeout'
163 * is zero. */
164int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
165 long long tval;
166 long double ftval;
167
168 if (unit == UNIT_SECONDS) {
169 if (getLongDoubleFromObjectOrReply(c,object,&ftval,
170 "timeout is not a float or out of range") != C_OK)
171 return C_ERR;
172 tval = (long long) (ftval * 1000.0);
173 } else {
174 if (getLongLongFromObjectOrReply(c,object,&tval,
175 "timeout is not an integer or out of range") != C_OK)
176 return C_ERR;
177 }
178
179 if (tval < 0) {
180 addReplyError(c,"timeout is negative");
181 return C_ERR;
182 }
183
184 if (tval > 0) {
185 tval += mstime();
186 }
187 *timeout = tval;
188
189 return C_OK;
190}
191