]>
Commit | Line | Data |
---|---|---|
4365e5b2 | 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 | ||
e2641e09 | 30 | #include "redis.h" |
31 | #include "sha1.h" /* SHA1 is used for DEBUG DIGEST */ | |
32 | ||
3688d7f3 | 33 | #include <arpa/inet.h> |
d4d20859 | 34 | #include <signal.h> |
35 | ||
36 | #ifdef HAVE_BACKTRACE | |
37 | #include <execinfo.h> | |
38 | #include <ucontext.h> | |
11bd247d | 39 | #include <fcntl.h> |
d7c7ac4a | 40 | #include "bio.h" |
d4d20859 | 41 | #endif /* HAVE_BACKTRACE */ |
3688d7f3 | 42 | |
e2641e09 | 43 | /* ================================= Debugging ============================== */ |
44 | ||
45 | /* Compute the sha1 of string at 's' with 'len' bytes long. | |
46 | * The SHA1 is then xored againt the string pointed by digest. | |
47 | * Since xor is commutative, this operation is used in order to | |
48 | * "add" digests relative to unordered elements. | |
49 | * | |
50 | * So digest(a,b,c,d) will be the same of digest(b,a,c,d) */ | |
51 | void xorDigest(unsigned char *digest, void *ptr, size_t len) { | |
52 | SHA1_CTX ctx; | |
53 | unsigned char hash[20], *s = ptr; | |
54 | int j; | |
55 | ||
56 | SHA1Init(&ctx); | |
57 | SHA1Update(&ctx,s,len); | |
58 | SHA1Final(hash,&ctx); | |
59 | ||
60 | for (j = 0; j < 20; j++) | |
61 | digest[j] ^= hash[j]; | |
62 | } | |
63 | ||
64 | void xorObjectDigest(unsigned char *digest, robj *o) { | |
65 | o = getDecodedObject(o); | |
66 | xorDigest(digest,o->ptr,sdslen(o->ptr)); | |
67 | decrRefCount(o); | |
68 | } | |
69 | ||
70 | /* This function instead of just computing the SHA1 and xoring it | |
71 | * against diget, also perform the digest of "digest" itself and | |
72 | * replace the old value with the new one. | |
73 | * | |
74 | * So the final digest will be: | |
75 | * | |
76 | * digest = SHA1(digest xor SHA1(data)) | |
77 | * | |
78 | * This function is used every time we want to preserve the order so | |
79 | * that digest(a,b,c,d) will be different than digest(b,c,d,a) | |
80 | * | |
81 | * Also note that mixdigest("foo") followed by mixdigest("bar") | |
82 | * will lead to a different digest compared to "fo", "obar". | |
83 | */ | |
84 | void mixDigest(unsigned char *digest, void *ptr, size_t len) { | |
85 | SHA1_CTX ctx; | |
86 | char *s = ptr; | |
87 | ||
88 | xorDigest(digest,s,len); | |
89 | SHA1Init(&ctx); | |
90 | SHA1Update(&ctx,digest,20); | |
91 | SHA1Final(digest,&ctx); | |
92 | } | |
93 | ||
94 | void mixObjectDigest(unsigned char *digest, robj *o) { | |
95 | o = getDecodedObject(o); | |
96 | mixDigest(digest,o->ptr,sdslen(o->ptr)); | |
97 | decrRefCount(o); | |
98 | } | |
99 | ||
100 | /* Compute the dataset digest. Since keys, sets elements, hashes elements | |
101 | * are not ordered, we use a trick: every aggregate digest is the xor | |
102 | * of the digests of their elements. This way the order will not change | |
103 | * the result. For list instead we use a feedback entering the output digest | |
104 | * as input in order to ensure that a different ordered list will result in | |
105 | * a different digest. */ | |
106 | void computeDatasetDigest(unsigned char *final) { | |
107 | unsigned char digest[20]; | |
108 | char buf[128]; | |
109 | dictIterator *di = NULL; | |
110 | dictEntry *de; | |
111 | int j; | |
112 | uint32_t aux; | |
113 | ||
114 | memset(final,0,20); /* Start with a clean result */ | |
115 | ||
116 | for (j = 0; j < server.dbnum; j++) { | |
117 | redisDb *db = server.db+j; | |
118 | ||
119 | if (dictSize(db->dict) == 0) continue; | |
120 | di = dictGetIterator(db->dict); | |
121 | ||
122 | /* hash the DB id, so the same dataset moved in a different | |
123 | * DB will lead to a different digest */ | |
124 | aux = htonl(j); | |
125 | mixDigest(final,&aux,sizeof(aux)); | |
126 | ||
127 | /* Iterate this DB writing every entry */ | |
128 | while((de = dictNext(di)) != NULL) { | |
129 | sds key; | |
130 | robj *keyobj, *o; | |
4be855e7 | 131 | long long expiretime; |
e2641e09 | 132 | |
133 | memset(digest,0,20); /* This key-val digest */ | |
c0ba9ebe | 134 | key = dictGetKey(de); |
e2641e09 | 135 | keyobj = createStringObject(key,sdslen(key)); |
136 | ||
137 | mixDigest(digest,key,sdslen(key)); | |
138 | ||
c0ba9ebe | 139 | o = dictGetVal(de); |
e2641e09 | 140 | |
141 | aux = htonl(o->type); | |
142 | mixDigest(digest,&aux,sizeof(aux)); | |
143 | expiretime = getExpire(db,keyobj); | |
144 | ||
145 | /* Save the key and associated value */ | |
146 | if (o->type == REDIS_STRING) { | |
147 | mixObjectDigest(digest,o); | |
148 | } else if (o->type == REDIS_LIST) { | |
149 | listTypeIterator *li = listTypeInitIterator(o,0,REDIS_TAIL); | |
150 | listTypeEntry entry; | |
151 | while(listTypeNext(li,&entry)) { | |
152 | robj *eleobj = listTypeGet(&entry); | |
153 | mixObjectDigest(digest,eleobj); | |
154 | decrRefCount(eleobj); | |
155 | } | |
156 | listTypeReleaseIterator(li); | |
157 | } else if (o->type == REDIS_SET) { | |
cb72d0f1 | 158 | setTypeIterator *si = setTypeInitIterator(o); |
2767f1c0 | 159 | robj *ele; |
1b508da7 | 160 | while((ele = setTypeNextObject(si)) != NULL) { |
2767f1c0 PN |
161 | xorObjectDigest(digest,ele); |
162 | decrRefCount(ele); | |
e2641e09 | 163 | } |
2767f1c0 | 164 | setTypeReleaseIterator(si); |
e2641e09 | 165 | } else if (o->type == REDIS_ZSET) { |
dddf5335 PN |
166 | unsigned char eledigest[20]; |
167 | ||
168 | if (o->encoding == REDIS_ENCODING_ZIPLIST) { | |
169 | unsigned char *zl = o->ptr; | |
170 | unsigned char *eptr, *sptr; | |
171 | unsigned char *vstr; | |
172 | unsigned int vlen; | |
173 | long long vll; | |
174 | double score; | |
175 | ||
176 | eptr = ziplistIndex(zl,0); | |
177 | redisAssert(eptr != NULL); | |
178 | sptr = ziplistNext(zl,eptr); | |
179 | redisAssert(sptr != NULL); | |
180 | ||
181 | while (eptr != NULL) { | |
182 | redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll)); | |
183 | score = zzlGetScore(sptr); | |
184 | ||
185 | memset(eledigest,0,20); | |
186 | if (vstr != NULL) { | |
187 | mixDigest(eledigest,vstr,vlen); | |
188 | } else { | |
189 | ll2string(buf,sizeof(buf),vll); | |
190 | mixDigest(eledigest,buf,strlen(buf)); | |
191 | } | |
192 | ||
193 | snprintf(buf,sizeof(buf),"%.17g",score); | |
194 | mixDigest(eledigest,buf,strlen(buf)); | |
195 | xorDigest(digest,eledigest,20); | |
196 | zzlNext(zl,&eptr,&sptr); | |
197 | } | |
100ed062 | 198 | } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { |
dddf5335 PN |
199 | zset *zs = o->ptr; |
200 | dictIterator *di = dictGetIterator(zs->dict); | |
201 | dictEntry *de; | |
202 | ||
203 | while((de = dictNext(di)) != NULL) { | |
c0ba9ebe | 204 | robj *eleobj = dictGetKey(de); |
205 | double *score = dictGetVal(de); | |
dddf5335 PN |
206 | |
207 | snprintf(buf,sizeof(buf),"%.17g",*score); | |
208 | memset(eledigest,0,20); | |
209 | mixObjectDigest(eledigest,eleobj); | |
210 | mixDigest(eledigest,buf,strlen(buf)); | |
211 | xorDigest(digest,eledigest,20); | |
212 | } | |
213 | dictReleaseIterator(di); | |
214 | } else { | |
215 | redisPanic("Unknown sorted set encoding"); | |
e2641e09 | 216 | } |
e2641e09 | 217 | } else if (o->type == REDIS_HASH) { |
218 | hashTypeIterator *hi; | |
219 | robj *obj; | |
220 | ||
221 | hi = hashTypeInitIterator(o); | |
222 | while (hashTypeNext(hi) != REDIS_ERR) { | |
223 | unsigned char eledigest[20]; | |
224 | ||
225 | memset(eledigest,0,20); | |
8c304be3 | 226 | obj = hashTypeCurrentObject(hi,REDIS_HASH_KEY); |
e2641e09 | 227 | mixObjectDigest(eledigest,obj); |
228 | decrRefCount(obj); | |
8c304be3 | 229 | obj = hashTypeCurrentObject(hi,REDIS_HASH_VALUE); |
e2641e09 | 230 | mixObjectDigest(eledigest,obj); |
231 | decrRefCount(obj); | |
232 | xorDigest(digest,eledigest,20); | |
233 | } | |
234 | hashTypeReleaseIterator(hi); | |
235 | } else { | |
236 | redisPanic("Unknown object type"); | |
237 | } | |
238 | /* If the key has an expire, add it to the mix */ | |
239 | if (expiretime != -1) xorDigest(digest,"!!expire!!",10); | |
240 | /* We can finally xor the key-val digest to the final digest */ | |
241 | xorDigest(final,digest,20); | |
242 | decrRefCount(keyobj); | |
243 | } | |
244 | dictReleaseIterator(di); | |
245 | } | |
246 | } | |
247 | ||
248 | void debugCommand(redisClient *c) { | |
249 | if (!strcasecmp(c->argv[1]->ptr,"segfault")) { | |
250 | *((char*)-1) = 'x'; | |
6fdc6354 | 251 | } else if (!strcasecmp(c->argv[1]->ptr,"oom")) { |
252 | void *ptr = zmalloc(ULONG_MAX); /* Should trigger an out of memory. */ | |
253 | zfree(ptr); | |
254 | addReply(c,shared.ok); | |
e3e69935 | 255 | } else if (!strcasecmp(c->argv[1]->ptr,"assert")) { |
256 | if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]); | |
bab205f7 | 257 | redisAssertWithInfo(c,c->argv[0],1 == 2); |
e2641e09 | 258 | } else if (!strcasecmp(c->argv[1]->ptr,"reload")) { |
f48cd4b9 | 259 | if (rdbSave(server.rdb_filename) != REDIS_OK) { |
e2641e09 | 260 | addReply(c,shared.err); |
261 | return; | |
262 | } | |
263 | emptyDb(); | |
f48cd4b9 | 264 | if (rdbLoad(server.rdb_filename) != REDIS_OK) { |
80ad7189 | 265 | addReplyError(c,"Error trying to load the RDB dump"); |
e2641e09 | 266 | return; |
267 | } | |
268 | redisLog(REDIS_WARNING,"DB reloaded by DEBUG RELOAD"); | |
269 | addReply(c,shared.ok); | |
270 | } else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) { | |
271 | emptyDb(); | |
2c915bcf | 272 | if (loadAppendOnlyFile(server.aof_filename) != REDIS_OK) { |
e2641e09 | 273 | addReply(c,shared.err); |
274 | return; | |
275 | } | |
efb60225 | 276 | server.dirty = 0; /* Prevent AOF / replication */ |
e2641e09 | 277 | redisLog(REDIS_WARNING,"Append Only File loaded by DEBUG LOADAOF"); |
278 | addReply(c,shared.ok); | |
279 | } else if (!strcasecmp(c->argv[1]->ptr,"object") && c->argc == 3) { | |
b39619d8 | 280 | dictEntry *de; |
e2641e09 | 281 | robj *val; |
5ef64098 | 282 | char *strenc; |
e2641e09 | 283 | |
b39619d8 | 284 | if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) { |
e2641e09 | 285 | addReply(c,shared.nokeyerr); |
286 | return; | |
287 | } | |
c0ba9ebe | 288 | val = dictGetVal(de); |
5ef64098 | 289 | strenc = strEncoding(val->encoding); |
3be00d7e | 290 | |
5ef64098 | 291 | addReplyStatusFormat(c, |
292 | "Value at:%p refcount:%d " | |
293 | "encoding:%s serializedlength:%lld " | |
3be00d7e | 294 | "lru:%d lru_seconds_idle:%lu", |
5ef64098 | 295 | (void*)val, val->refcount, |
296 | strenc, (long long) rdbSavedObjectLen(val), | |
3be00d7e | 297 | val->lru, estimateObjectIdleTime(val)); |
e2641e09 | 298 | } else if (!strcasecmp(c->argv[1]->ptr,"populate") && c->argc == 3) { |
299 | long keys, j; | |
300 | robj *key, *val; | |
301 | char buf[128]; | |
302 | ||
303 | if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK) | |
304 | return; | |
305 | for (j = 0; j < keys; j++) { | |
306 | snprintf(buf,sizeof(buf),"key:%lu",j); | |
307 | key = createStringObject(buf,strlen(buf)); | |
308 | if (lookupKeyRead(c->db,key) != NULL) { | |
309 | decrRefCount(key); | |
310 | continue; | |
311 | } | |
312 | snprintf(buf,sizeof(buf),"value:%lu",j); | |
313 | val = createStringObject(buf,strlen(buf)); | |
314 | dbAdd(c->db,key,val); | |
315 | decrRefCount(key); | |
316 | } | |
317 | addReply(c,shared.ok); | |
318 | } else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) { | |
319 | unsigned char digest[20]; | |
3ab20376 | 320 | sds d = sdsempty(); |
e2641e09 | 321 | int j; |
322 | ||
323 | computeDatasetDigest(digest); | |
324 | for (j = 0; j < 20; j++) | |
325 | d = sdscatprintf(d, "%02x",digest[j]); | |
3ab20376 PN |
326 | addReplyStatus(c,d); |
327 | sdsfree(d); | |
404345d8 | 328 | } else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) { |
329 | double dtime = strtod(c->argv[2]->ptr,NULL); | |
330 | long long utime = dtime*1000000; | |
331 | ||
332 | usleep(utime); | |
333 | addReply(c,shared.ok); | |
e2641e09 | 334 | } else { |
3ab20376 PN |
335 | addReplyError(c, |
336 | "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]"); | |
e2641e09 | 337 | } |
338 | } | |
339 | ||
d4d20859 | 340 | /* =========================== Crash handling ============================== */ |
341 | ||
e2641e09 | 342 | void _redisAssert(char *estr, char *file, int line) { |
fa5af017 | 343 | bugReportStart(); |
e2641e09 | 344 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED ==="); |
345 | redisLog(REDIS_WARNING,"==> %s:%d '%s' is not true",file,line,estr); | |
346 | #ifdef HAVE_BACKTRACE | |
fa5af017 | 347 | server.assert_failed = estr; |
348 | server.assert_file = file; | |
349 | server.assert_line = line; | |
350 | redisLog(REDIS_WARNING,"(forcing SIGSEGV to print the bug report.)"); | |
e2641e09 | 351 | #endif |
57be4775 | 352 | *((char*)-1) = 'x'; |
e2641e09 | 353 | } |
354 | ||
e3e69935 | 355 | void _redisAssertPrintClientInfo(redisClient *c) { |
bab205f7 | 356 | int j; |
e3e69935 | 357 | |
fa5af017 | 358 | bugReportStart(); |
bab205f7 | 359 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); |
360 | redisLog(REDIS_WARNING,"client->flags = %d", c->flags); | |
361 | redisLog(REDIS_WARNING,"client->fd = %d", c->fd); | |
362 | redisLog(REDIS_WARNING,"client->argc = %d", c->argc); | |
363 | for (j=0; j < c->argc; j++) { | |
364 | char buf[128]; | |
365 | char *arg; | |
366 | ||
367 | if (c->argv[j]->type == REDIS_STRING && | |
368 | c->argv[j]->encoding == REDIS_ENCODING_RAW) | |
369 | { | |
370 | arg = (char*) c->argv[j]->ptr; | |
371 | } else { | |
372 | snprintf(buf,sizeof(buf),"Object type: %d, encoding: %d", | |
373 | c->argv[j]->type, c->argv[j]->encoding); | |
374 | arg = buf; | |
e3e69935 | 375 | } |
bab205f7 | 376 | redisLog(REDIS_WARNING,"client->argv[%d] = \"%s\" (refcount: %d)", |
377 | j, arg, c->argv[j]->refcount); | |
378 | } | |
379 | } | |
380 | ||
00010fa9 | 381 | void redisLogObjectDebugInfo(robj *o) { |
bab205f7 | 382 | redisLog(REDIS_WARNING,"Object type: %d", o->type); |
383 | redisLog(REDIS_WARNING,"Object encoding: %d", o->encoding); | |
384 | redisLog(REDIS_WARNING,"Object refcount: %d", o->refcount); | |
385 | if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_RAW) { | |
386 | redisLog(REDIS_WARNING,"Object raw string len: %d", sdslen(o->ptr)); | |
387 | if (sdslen(o->ptr) < 4096) | |
388 | redisLog(REDIS_WARNING,"Object raw string content: \"%s\"", (char*)o->ptr); | |
00010fa9 | 389 | } else if (o->type == REDIS_LIST) { |
390 | redisLog(REDIS_WARNING,"List length: %d", (int) listTypeLength(o)); | |
391 | } else if (o->type == REDIS_SET) { | |
392 | redisLog(REDIS_WARNING,"Set size: %d", (int) setTypeSize(o)); | |
393 | } else if (o->type == REDIS_HASH) { | |
394 | redisLog(REDIS_WARNING,"Hash size: %d", (int) hashTypeLength(o)); | |
395 | } else if (o->type == REDIS_ZSET) { | |
396 | redisLog(REDIS_WARNING,"Sorted set size: %d", (int) zsetLength(o)); | |
397 | if (o->encoding == REDIS_ENCODING_SKIPLIST) | |
398 | redisLog(REDIS_WARNING,"Skiplist level: %d", (int) ((zset*)o->ptr)->zsl->level); | |
e3e69935 | 399 | } |
400 | } | |
401 | ||
00010fa9 | 402 | void _redisAssertPrintObject(robj *o) { |
403 | bugReportStart(); | |
404 | redisLog(REDIS_WARNING,"=== ASSERTION FAILED OBJECT CONTEXT ==="); | |
405 | redisLogObjectDebugInfo(o); | |
406 | } | |
407 | ||
bab205f7 | 408 | void _redisAssertWithInfo(redisClient *c, robj *o, char *estr, char *file, int line) { |
409 | if (c) _redisAssertPrintClientInfo(c); | |
410 | if (o) _redisAssertPrintObject(o); | |
e3e69935 | 411 | _redisAssert(estr,file,line); |
412 | } | |
413 | ||
e2641e09 | 414 | void _redisPanic(char *msg, char *file, int line) { |
fa5af017 | 415 | bugReportStart(); |
120b9ba8 | 416 | redisLog(REDIS_WARNING,"------------------------------------------------"); |
e2641e09 | 417 | redisLog(REDIS_WARNING,"!!! Software Failure. Press left mouse button to continue"); |
418 | redisLog(REDIS_WARNING,"Guru Meditation: %s #%s:%d",msg,file,line); | |
419 | #ifdef HAVE_BACKTRACE | |
420 | redisLog(REDIS_WARNING,"(forcing SIGSEGV in order to print the stack trace)"); | |
57be4775 | 421 | #endif |
120b9ba8 | 422 | redisLog(REDIS_WARNING,"------------------------------------------------"); |
e2641e09 | 423 | *((char*)-1) = 'x'; |
e2641e09 | 424 | } |
d4d20859 | 425 | |
ac834d23 | 426 | void bugReportStart(void) { |
427 | if (server.bug_report_start == 0) { | |
428 | redisLog(REDIS_WARNING, | |
429 | "\n\n=== REDIS BUG REPORT START: Cut & paste starting from here ==="); | |
430 | server.bug_report_start = 1; | |
431 | } | |
432 | } | |
433 | ||
d4d20859 | 434 | #ifdef HAVE_BACKTRACE |
435 | static void *getMcontextEip(ucontext_t *uc) { | |
a66a4963 | 436 | #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) |
437 | /* OSX < 10.6 */ | |
438 | #if defined(__x86_64__) | |
d4d20859 | 439 | return (void*) uc->uc_mcontext->__ss.__rip; |
a66a4963 | 440 | #elif defined(__i386__) |
d4d20859 | 441 | return (void*) uc->uc_mcontext->__ss.__eip; |
a66a4963 | 442 | #else |
d4d20859 | 443 | return (void*) uc->uc_mcontext->__ss.__srr0; |
a66a4963 | 444 | #endif |
d4d20859 | 445 | #elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) |
a66a4963 | 446 | /* OSX >= 10.6 */ |
447 | #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) | |
d4d20859 | 448 | return (void*) uc->uc_mcontext->__ss.__rip; |
a66a4963 | 449 | #else |
d4d20859 | 450 | return (void*) uc->uc_mcontext->__ss.__eip; |
a66a4963 | 451 | #endif |
452 | #elif defined(__linux__) | |
453 | /* Linux */ | |
454 | #if defined(__i386__) | |
d4d20859 | 455 | return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */ |
a66a4963 | 456 | #elif defined(__X86_64__) || defined(__x86_64__) |
d4d20859 | 457 | return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */ |
a66a4963 | 458 | #elif defined(__ia64__) /* Linux IA64 */ |
d4d20859 | 459 | return (void*) uc->uc_mcontext.sc_ip; |
a66a4963 | 460 | #endif |
d4d20859 | 461 | #else |
462 | return NULL; | |
463 | #endif | |
464 | } | |
465 | ||
d4d20859 | 466 | void logStackContent(void **sp) { |
467 | int i; | |
468 | for (i = 15; i >= 0; i--) { | |
447ebf3b | 469 | if (sizeof(long) == 4) |
470 | redisLog(REDIS_WARNING, "(%08lx) -> %08lx", sp+i, sp[i]); | |
471 | else | |
472 | redisLog(REDIS_WARNING, "(%016lx) -> %016lx", sp+i, sp[i]); | |
d4d20859 | 473 | } |
474 | } | |
475 | ||
476 | void logRegisters(ucontext_t *uc) { | |
477 | redisLog(REDIS_WARNING, "--- REGISTERS"); | |
a66a4963 | 478 | |
479 | /* OSX */ | |
d4d20859 | 480 | #if defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6) |
a66a4963 | 481 | /* OSX AMD64 */ |
482 | #if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__) | |
d4d20859 | 483 | redisLog(REDIS_WARNING, |
484 | "\n" | |
447ebf3b | 485 | "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n" |
486 | "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n" | |
487 | "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n" | |
488 | "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n" | |
489 | "RIP:%016lx EFL:%016lx\nCS :%016lx FS:%016lx GS:%016lx", | |
d4d20859 | 490 | uc->uc_mcontext->__ss.__rax, |
491 | uc->uc_mcontext->__ss.__rbx, | |
492 | uc->uc_mcontext->__ss.__rcx, | |
493 | uc->uc_mcontext->__ss.__rdx, | |
494 | uc->uc_mcontext->__ss.__rdi, | |
495 | uc->uc_mcontext->__ss.__rsi, | |
496 | uc->uc_mcontext->__ss.__rbp, | |
497 | uc->uc_mcontext->__ss.__rsp, | |
498 | uc->uc_mcontext->__ss.__r8, | |
499 | uc->uc_mcontext->__ss.__r9, | |
500 | uc->uc_mcontext->__ss.__r10, | |
501 | uc->uc_mcontext->__ss.__r11, | |
502 | uc->uc_mcontext->__ss.__r12, | |
503 | uc->uc_mcontext->__ss.__r13, | |
504 | uc->uc_mcontext->__ss.__r14, | |
505 | uc->uc_mcontext->__ss.__r15, | |
506 | uc->uc_mcontext->__ss.__rip, | |
507 | uc->uc_mcontext->__ss.__rflags, | |
508 | uc->uc_mcontext->__ss.__cs, | |
509 | uc->uc_mcontext->__ss.__fs, | |
510 | uc->uc_mcontext->__ss.__gs | |
511 | ); | |
512 | logStackContent((void**)uc->uc_mcontext->__ss.__rsp); | |
a66a4963 | 513 | #else |
514 | /* OSX x86 */ | |
d4d20859 | 515 | redisLog(REDIS_WARNING, |
516 | "\n" | |
447ebf3b | 517 | "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n" |
518 | "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n" | |
519 | "SS:%08lx EFL:%08lx EIP:%08lx CS :%08lx\n" | |
520 | "DS:%08lx ES:%08lx FS :%08lx GS :%08lx", | |
d4d20859 | 521 | uc->uc_mcontext->__ss.__eax, |
522 | uc->uc_mcontext->__ss.__ebx, | |
523 | uc->uc_mcontext->__ss.__ecx, | |
524 | uc->uc_mcontext->__ss.__edx, | |
525 | uc->uc_mcontext->__ss.__edi, | |
526 | uc->uc_mcontext->__ss.__esi, | |
527 | uc->uc_mcontext->__ss.__ebp, | |
528 | uc->uc_mcontext->__ss.__esp, | |
529 | uc->uc_mcontext->__ss.__ss, | |
530 | uc->uc_mcontext->__ss.__eflags, | |
531 | uc->uc_mcontext->__ss.__eip, | |
532 | uc->uc_mcontext->__ss.__cs, | |
533 | uc->uc_mcontext->__ss.__ds, | |
534 | uc->uc_mcontext->__ss.__es, | |
535 | uc->uc_mcontext->__ss.__fs, | |
536 | uc->uc_mcontext->__ss.__gs | |
537 | ); | |
538 | logStackContent((void**)uc->uc_mcontext->__ss.__esp); | |
a66a4963 | 539 | #endif |
540 | /* Linux */ | |
541 | #elif defined(__linux__) | |
542 | /* Linux x86 */ | |
543 | #if defined(__i386__) | |
632da605 | 544 | redisLog(REDIS_WARNING, |
545 | "\n" | |
447ebf3b | 546 | "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n" |
547 | "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n" | |
548 | "SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n" | |
549 | "DS :%08lx ES :%08lx FS :%08lx GS:%08lx", | |
632da605 | 550 | uc->uc_mcontext.gregs[11], |
551 | uc->uc_mcontext.gregs[8], | |
552 | uc->uc_mcontext.gregs[10], | |
553 | uc->uc_mcontext.gregs[9], | |
554 | uc->uc_mcontext.gregs[4], | |
555 | uc->uc_mcontext.gregs[5], | |
556 | uc->uc_mcontext.gregs[6], | |
557 | uc->uc_mcontext.gregs[7], | |
558 | uc->uc_mcontext.gregs[18], | |
559 | uc->uc_mcontext.gregs[17], | |
560 | uc->uc_mcontext.gregs[14], | |
561 | uc->uc_mcontext.gregs[15], | |
562 | uc->uc_mcontext.gregs[3], | |
563 | uc->uc_mcontext.gregs[2], | |
564 | uc->uc_mcontext.gregs[1], | |
565 | uc->uc_mcontext.gregs[0] | |
566 | ); | |
567 | logStackContent((void**)uc->uc_mcontext.gregs[7]); | |
a66a4963 | 568 | #elif defined(__X86_64__) || defined(__x86_64__) |
569 | /* Linux AMD64 */ | |
eea8c7a4 | 570 | redisLog(REDIS_WARNING, |
571 | "\n" | |
447ebf3b | 572 | "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n" |
573 | "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n" | |
574 | "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n" | |
575 | "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n" | |
576 | "RIP:%016lx EFL:%016lx\nCSGSFS:%016lx", | |
eea8c7a4 | 577 | uc->uc_mcontext.gregs[13], |
578 | uc->uc_mcontext.gregs[11], | |
579 | uc->uc_mcontext.gregs[14], | |
580 | uc->uc_mcontext.gregs[12], | |
581 | uc->uc_mcontext.gregs[8], | |
582 | uc->uc_mcontext.gregs[9], | |
583 | uc->uc_mcontext.gregs[10], | |
584 | uc->uc_mcontext.gregs[15], | |
585 | uc->uc_mcontext.gregs[0], | |
586 | uc->uc_mcontext.gregs[1], | |
587 | uc->uc_mcontext.gregs[2], | |
588 | uc->uc_mcontext.gregs[3], | |
589 | uc->uc_mcontext.gregs[4], | |
590 | uc->uc_mcontext.gregs[5], | |
591 | uc->uc_mcontext.gregs[6], | |
592 | uc->uc_mcontext.gregs[7], | |
593 | uc->uc_mcontext.gregs[16], | |
594 | uc->uc_mcontext.gregs[17], | |
595 | uc->uc_mcontext.gregs[18] | |
596 | ); | |
597 | logStackContent((void**)uc->uc_mcontext.gregs[15]); | |
a66a4963 | 598 | #endif |
d4d20859 | 599 | #else |
600 | redisLog(REDIS_WARNING, | |
601 | " Dumping of registers not supported for this OS/arch"); | |
602 | #endif | |
603 | } | |
604 | ||
11bd247d | 605 | /* Logs the stack trace using the backtrace() call. This function is designed |
606 | * to be called from signal handlers safely. */ | |
607 | void logStackTrace(ucontext_t *uc) { | |
d4d20859 | 608 | void *trace[100]; |
11bd247d | 609 | int trace_size = 0, fd; |
610 | ||
611 | /* Open the log file in append mode. */ | |
612 | fd = server.logfile ? | |
613 | open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) : | |
614 | STDOUT_FILENO; | |
615 | if (fd == -1) return; | |
d4d20859 | 616 | |
617 | /* Generate the stack trace */ | |
618 | trace_size = backtrace(trace, 100); | |
619 | ||
620 | /* overwrite sigaction with caller's address */ | |
11bd247d | 621 | if (getMcontextEip(uc) != NULL) |
d4d20859 | 622 | trace[1] = getMcontextEip(uc); |
11bd247d | 623 | |
624 | /* Write symbols to log file */ | |
625 | backtrace_symbols_fd(trace, trace_size, fd); | |
626 | ||
627 | /* Cleanup */ | |
628 | if (server.logfile) close(fd); | |
a3238704 | 629 | } |
630 | ||
631 | /* Log information about the "current" client, that is, the client that is | |
632 | * currently being served by Redis. May be NULL if Redis is not serving a | |
633 | * client right now. */ | |
634 | void logCurrentClient(void) { | |
635 | if (server.current_client == NULL) return; | |
636 | ||
637 | redisClient *cc = server.current_client; | |
638 | sds client; | |
639 | int j; | |
640 | ||
641 | redisLog(REDIS_WARNING, "--- CURRENT CLIENT INFO"); | |
642 | client = getClientInfoString(cc); | |
643 | redisLog(REDIS_WARNING,"client: %s", client); | |
644 | sdsfree(client); | |
645 | for (j = 0; j < cc->argc; j++) { | |
646 | robj *decoded; | |
647 | ||
648 | decoded = getDecodedObject(cc->argv[j]); | |
649 | redisLog(REDIS_WARNING,"argv[%d]: '%s'", j, (char*)decoded->ptr); | |
650 | decrRefCount(decoded); | |
651 | } | |
652 | /* Check if the first argument, usually a key, is found inside the | |
653 | * selected DB, and if so print info about the associated object. */ | |
654 | if (cc->argc >= 1) { | |
655 | robj *val, *key; | |
656 | dictEntry *de; | |
657 | ||
658 | key = getDecodedObject(cc->argv[1]); | |
659 | de = dictFind(cc->db->dict, key->ptr); | |
660 | if (de) { | |
661 | val = dictGetVal(de); | |
662 | redisLog(REDIS_WARNING,"key '%s' found in DB containing the following object:", key->ptr); | |
663 | redisLogObjectDebugInfo(val); | |
664 | } | |
665 | decrRefCount(key); | |
666 | } | |
667 | } | |
668 | ||
5a9e3f58 | 669 | #if defined(HAVE_PROC_MAPS) |
b1b602a9 | 670 | uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); |
671 | void memtest_non_destructive_invert(void *addr, size_t size); | |
672 | void memtest_non_destructive_swap(void *addr, size_t size); | |
673 | #define MEMTEST_MAX_REGIONS 128 | |
5a9e3f58 | 674 | |
675 | int memtest_test_linux_anonymous_maps(void) { | |
676 | FILE *fp = fopen("/proc/self/maps","r"); | |
677 | char line[1024]; | |
678 | size_t start_addr, end_addr, size; | |
b1b602a9 | 679 | size_t start_vect[MEMTEST_MAX_REGIONS]; |
680 | size_t size_vect[MEMTEST_MAX_REGIONS]; | |
681 | int regions = 0, j; | |
682 | uint64_t crc1 = 0, crc2 = 0, crc3 = 0; | |
5a9e3f58 | 683 | |
684 | while(fgets(line,sizeof(line),fp) != NULL) { | |
685 | char *start, *end, *p = line; | |
5a9e3f58 | 686 | |
687 | start = p; | |
688 | p = strchr(p,'-'); | |
689 | if (!p) continue; | |
690 | *p++ = '\0'; | |
691 | end = p; | |
692 | p = strchr(p,' '); | |
693 | if (!p) continue; | |
694 | *p++ = '\0'; | |
695 | if (strstr(p,"stack") || | |
696 | strstr(p,"vdso") || | |
697 | strstr(p,"vsyscall")) continue; | |
698 | if (!strstr(p,"00:00")) continue; | |
699 | if (!strstr(p,"rw")) continue; | |
700 | ||
701 | start_addr = strtoul(start,NULL,16); | |
702 | end_addr = strtoul(end,NULL,16); | |
703 | size = end_addr-start_addr; | |
b1b602a9 | 704 | |
705 | start_vect[regions] = start_addr; | |
706 | size_vect[regions] = size; | |
707 | printf("Testing %lx %lu\n", start_vect[regions], size_vect[regions]); | |
708 | regions++; | |
5a9e3f58 | 709 | } |
b1b602a9 | 710 | |
711 | /* Test all the regions as an unique sequential region. | |
712 | * 1) Take the CRC64 of the memory region. */ | |
713 | for (j = 0; j < regions; j++) { | |
714 | crc1 = crc64(crc1,(void*)start_vect[j],size_vect[j]); | |
715 | } | |
716 | ||
717 | /* 2) Invert bits, swap adiacent words, swap again, invert bits. | |
718 | * This is the error amplification step. */ | |
719 | for (j = 0; j < regions; j++) | |
720 | memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]); | |
721 | for (j = 0; j < regions; j++) | |
722 | memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]); | |
723 | for (j = 0; j < regions; j++) | |
724 | memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]); | |
725 | for (j = 0; j < regions; j++) | |
726 | memtest_non_destructive_invert((void*)start_vect[j],size_vect[j]); | |
727 | ||
728 | /* 3) Take the CRC64 sum again. */ | |
729 | for (j = 0; j < regions; j++) | |
730 | crc2 = crc64(crc2,(void*)start_vect[j],size_vect[j]); | |
731 | ||
732 | /* 4) Swap + Swap again */ | |
733 | for (j = 0; j < regions; j++) | |
734 | memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]); | |
735 | for (j = 0; j < regions; j++) | |
736 | memtest_non_destructive_swap((void*)start_vect[j],size_vect[j]); | |
737 | ||
738 | /* 5) Take the CRC64 sum again. */ | |
739 | for (j = 0; j < regions; j++) | |
740 | crc3 = crc64(crc3,(void*)start_vect[j],size_vect[j]); | |
741 | ||
742 | /* NOTE: It is very important to close the file descriptor only now | |
743 | * because closing it before may result into unmapping of some memory | |
744 | * region that we are testing. */ | |
5a9e3f58 | 745 | fclose(fp); |
b1b602a9 | 746 | |
747 | /* If the two CRC are not the same, we trapped a memory error. */ | |
748 | return crc1 != crc2 || crc2 != crc3; | |
5a9e3f58 | 749 | } |
750 | #endif | |
751 | ||
a3238704 | 752 | void sigsegvHandler(int sig, siginfo_t *info, void *secret) { |
753 | ucontext_t *uc = (ucontext_t*) secret; | |
11bd247d | 754 | sds infostring, clients; |
a3238704 | 755 | struct sigaction act; |
756 | REDIS_NOTUSED(info); | |
757 | ||
758 | bugReportStart(); | |
759 | redisLog(REDIS_WARNING, | |
760 | " Redis %s crashed by signal: %d", REDIS_VERSION, sig); | |
761 | redisLog(REDIS_WARNING, | |
762 | " Failed assertion: %s (%s:%d)", server.assert_failed, | |
763 | server.assert_file, server.assert_line); | |
764 | ||
765 | /* Log the stack trace */ | |
11bd247d | 766 | redisLog(REDIS_WARNING, "--- STACK TRACE"); |
767 | logStackTrace(uc); | |
d4d20859 | 768 | |
769 | /* Log INFO and CLIENT LIST */ | |
770 | redisLog(REDIS_WARNING, "--- INFO OUTPUT"); | |
771 | infostring = genRedisInfoString("all"); | |
a48c8d87 | 772 | infostring = sdscatprintf(infostring, "hash_init_value: %u\n", |
773 | dictGetHashFunctionSeed()); | |
d4d20859 | 774 | redisLogRaw(REDIS_WARNING, infostring); |
775 | redisLog(REDIS_WARNING, "--- CLIENT LIST OUTPUT"); | |
776 | clients = getAllClientsInfoString(); | |
777 | redisLogRaw(REDIS_WARNING, clients); | |
a3238704 | 778 | sdsfree(infostring); |
779 | sdsfree(clients); | |
d4d20859 | 780 | |
a3238704 | 781 | /* Log the current client */ |
782 | logCurrentClient(); | |
d4d20859 | 783 | |
784 | /* Log dump of processor registers */ | |
785 | logRegisters(uc); | |
786 | ||
5a9e3f58 | 787 | #if defined(HAVE_PROC_MAPS) |
788 | /* Test memory */ | |
789 | redisLog(REDIS_WARNING, "--- FAST MEMORY TEST"); | |
75369917 | 790 | bioKillThreads(); |
5a9e3f58 | 791 | if (memtest_test_linux_anonymous_maps()) { |
792 | redisLog(REDIS_WARNING, | |
793 | "!!! MEMORY ERROR DETECTED! Check your memory ASAP !!!"); | |
794 | } else { | |
795 | redisLog(REDIS_WARNING, | |
796 | "Fast memory test PASSED, however your memory can still be broken. Please run a memory test for several hours if possible."); | |
797 | } | |
798 | #endif | |
799 | ||
d4d20859 | 800 | redisLog(REDIS_WARNING, |
801 | "\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n" | |
525be599 | 802 | " Please report the crash opening an issue on github:\n\n" |
803 | " http://github.com/antirez/redis/issues\n\n" | |
804 | " Suspect RAM error? Use redis-server --test-memory to veryfy it.\n\n" | |
d4d20859 | 805 | ); |
806 | /* free(messages); Don't call free() with possibly corrupted memory. */ | |
807 | if (server.daemonize) unlink(server.pidfile); | |
808 | ||
809 | /* Make sure we exit with the right signal at the end. So for instance | |
810 | * the core will be dumped if enabled. */ | |
811 | sigemptyset (&act.sa_mask); | |
d4d20859 | 812 | act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND; |
813 | act.sa_handler = SIG_DFL; | |
814 | sigaction (sig, &act, NULL); | |
815 | kill(getpid(),sig); | |
816 | } | |
817 | #endif /* HAVE_BACKTRACE */ | |
39bd025c | 818 | |
ee789e15 | 819 | /* ==================== Logging functions for debugging ===================== */ |
820 | ||
821 | void redisLogHexDump(int level, char *descr, void *value, size_t len) { | |
822 | char buf[65], *b; | |
823 | unsigned char *v = value; | |
824 | char charset[] = "0123456789abcdef"; | |
825 | ||
826 | redisLog(level,"%s (hexdump):", descr); | |
827 | b = buf; | |
828 | while(len) { | |
829 | b[0] = charset[(*v)>>4]; | |
830 | b[1] = charset[(*v)&0xf]; | |
831 | b[2] = '\0'; | |
832 | b += 2; | |
833 | len--; | |
834 | v++; | |
835 | if (b-buf == 64 || len == 0) { | |
836 | redisLogRaw(level|REDIS_LOG_RAW,buf); | |
837 | b = buf; | |
838 | } | |
839 | } | |
840 | redisLogRaw(level|REDIS_LOG_RAW,"\n"); | |
841 | } | |
842 | ||
39bd025c | 843 | /* =========================== Software Watchdog ============================ */ |
844 | #include <sys/time.h> | |
845 | ||
846 | void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) { | |
a66a4963 | 847 | #ifdef HAVE_BACKTRACE |
39bd025c | 848 | ucontext_t *uc = (ucontext_t*) secret; |
a66a4963 | 849 | #endif |
39bd025c | 850 | REDIS_NOTUSED(info); |
851 | REDIS_NOTUSED(sig); | |
852 | ||
11bd247d | 853 | redisLogFromHandler(REDIS_WARNING,"\n--- WATCHDOG TIMER EXPIRED ---"); |
39bd025c | 854 | #ifdef HAVE_BACKTRACE |
11bd247d | 855 | logStackTrace(uc); |
23c0cdd2 | 856 | #else |
11bd247d | 857 | redisLogFromHandler(REDIS_WARNING,"Sorry: no support for backtrace()."); |
39bd025c | 858 | #endif |
11bd247d | 859 | redisLogFromHandler(REDIS_WARNING,"--------\n"); |
39bd025c | 860 | } |
861 | ||
862 | /* Schedule a SIGALRM delivery after the specified period in milliseconds. | |
863 | * If a timer is already scheduled, this function will re-schedule it to the | |
864 | * specified time. If period is 0 the current timer is disabled. */ | |
865 | void watchdogScheduleSignal(int period) { | |
866 | struct itimerval it; | |
867 | ||
868 | /* Will stop the timer if period is 0. */ | |
869 | it.it_value.tv_sec = period/1000; | |
a354da9a | 870 | it.it_value.tv_usec = (period%1000)*1000; |
39bd025c | 871 | /* Don't automatically restart. */ |
872 | it.it_interval.tv_sec = 0; | |
873 | it.it_interval.tv_usec = 0; | |
874 | setitimer(ITIMER_REAL, &it, NULL); | |
875 | } | |
876 | ||
504e5072 | 877 | /* Enable the software watchdog with the specified period in milliseconds. */ |
39bd025c | 878 | void enableWatchdog(int period) { |
61daf891 | 879 | int min_period; |
880 | ||
39bd025c | 881 | if (server.watchdog_period == 0) { |
882 | struct sigaction act; | |
883 | ||
884 | /* Watchdog was actually disabled, so we have to setup the signal | |
885 | * handler. */ | |
886 | sigemptyset(&act.sa_mask); | |
887 | act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO; | |
888 | act.sa_sigaction = watchdogSignalHandler; | |
889 | sigaction(SIGALRM, &act, NULL); | |
890 | } | |
61daf891 | 891 | /* If the configured period is smaller than twice the timer period, it is |
892 | * too short for the software watchdog to work reliably. Fix it now | |
893 | * if needed. */ | |
894 | min_period = (1000/REDIS_HZ)*2; | |
895 | if (period < min_period) period = min_period; | |
39bd025c | 896 | watchdogScheduleSignal(period); /* Adjust the current timer. */ |
897 | server.watchdog_period = period; | |
898 | } | |
899 | ||
900 | /* Disable the software watchdog. */ | |
901 | void disableWatchdog(void) { | |
902 | struct sigaction act; | |
903 | if (server.watchdog_period == 0) return; /* Already disabled. */ | |
904 | watchdogScheduleSignal(0); /* Stop the current timer. */ | |
905 | ||
906 | /* Set the signal handler to SIG_IGN, this will also remove pending | |
907 | * signals from the queue. */ | |
908 | sigemptyset(&act.sa_mask); | |
909 | act.sa_flags = 0; | |
910 | act.sa_handler = SIG_IGN; | |
911 | sigaction(SIGALRM, &act, NULL); | |
912 | server.watchdog_period = 0; | |
913 | } |