Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Backport binary TOUCH/GAT/GATQ commands

Taken from the 1.6 branch, partly written by Trond. I hope the CAS handling is
correct.
  • Loading branch information...
commit d87f568a95ed1abe468188476fa5ed9288799223 1 parent 51c8f31
dormando dormando authored
8 items.c
@@ -531,6 +531,14 @@ item *do_item_get(const char *key, const size_t nkey) {
531 531 return it;
532 532 }
533 533
  534 +item *do_item_touch(const char *key, size_t nkey, uint32_t exptime) {
  535 + item *it = do_item_get(key, nkey);
  536 + if (it != NULL) {
  537 + it->exptime = exptime;
  538 + }
  539 + return it;
  540 +}
  541 +
534 542 /** returns an item whether or not it's expired. */
535 543 item *do_item_get_nocheck(const char *key, const size_t nkey) {
536 544 item *it = assoc_find(key, nkey);
1  items.h
@@ -21,5 +21,6 @@ void do_item_flush_expired(void);
21 21
22 22 item *do_item_get(const char *key, const size_t nkey);
23 23 item *do_item_get_nocheck(const char *key, const size_t nkey);
  24 +item *do_item_touch(const char *key, const size_t nkey, uint32_t exptime);
24 25 void item_stats_reset(void);
25 26 extern pthread_mutex_t cache_lock;
91 memcached.c
@@ -166,6 +166,7 @@ static rel_time_t realtime(const time_t exptime) {
166 166 static void stats_init(void) {
167 167 stats.curr_items = stats.total_items = stats.curr_conns = stats.total_conns = stats.conn_structs = 0;
168 168 stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = stats.evictions = stats.reclaimed = 0;
  169 + stats.touch_cmds = stats.touch_misses = 0;
169 170 stats.curr_bytes = stats.listen_disabled_num = 0;
170 171 stats.accepting_conns = true; /* assuming we start in this state. */
171 172
@@ -1178,6 +1179,78 @@ static void complete_update_bin(conn *c) {
1178 1179 c->item = 0;
1179 1180 }
1180 1181
  1182 +static void process_bin_touch(conn *c) {
  1183 + item *it;
  1184 +
  1185 + protocol_binary_response_get* rsp = (protocol_binary_response_get*)c->wbuf;
  1186 + char* key = binary_get_key(c);
  1187 + size_t nkey = c->binary_header.request.keylen;
  1188 + protocol_binary_request_touch *t = (void *)&c->binary_header;
  1189 + uint32_t exptime = ntohl(t->message.body.expiration);
  1190 +
  1191 + if (settings.verbose > 1) {
  1192 + int ii;
  1193 + /* May be GAT/GATQ/etc */
  1194 + fprintf(stderr, "<%d TOUCH ", c->sfd);
  1195 + for (ii = 0; ii < nkey; ++ii) {
  1196 + fprintf(stderr, "%c", key[ii]);
  1197 + }
  1198 + fprintf(stderr, "\n");
  1199 + }
  1200 +
  1201 + it = item_touch(key, nkey, exptime);
  1202 +
  1203 + if (it) {
  1204 + /* the length has two unnecessary bytes ("\r\n") */
  1205 + uint16_t keylen = 0;
  1206 + uint32_t bodylen = sizeof(rsp->message.body) + (it->nbytes - 2);
  1207 +
  1208 + pthread_mutex_lock(&c->thread->stats.mutex);
  1209 + c->thread->stats.touch_cmds++;
  1210 + c->thread->stats.slab_stats[it->slabs_clsid].touch_hits++;
  1211 + pthread_mutex_unlock(&c->thread->stats.mutex);
  1212 +
  1213 + MEMCACHED_COMMAND_TOUCH(c->sfd, ITEM_key(it), it->nkey,
  1214 + it->nbytes, ITEM_get_cas(it));
  1215 +
  1216 + if (c->cmd == PROTOCOL_BINARY_CMD_TOUCH) {
  1217 + bodylen -= it->nbytes - 2;
  1218 + }
  1219 +
  1220 + add_bin_header(c, 0, sizeof(rsp->message.body), keylen, bodylen);
  1221 + rsp->message.header.response.cas = htonll(ITEM_get_cas(it));
  1222 +
  1223 + // add the flags
  1224 + rsp->message.body.flags = htonl(strtoul(ITEM_suffix(it), NULL, 10));
  1225 + add_iov(c, &rsp->message.body, sizeof(rsp->message.body));
  1226 +
  1227 + /* Add the data minus the CRLF */
  1228 + if (c->cmd != PROTOCOL_BINARY_CMD_TOUCH) {
  1229 + add_iov(c, ITEM_data(it), it->nbytes - 2);
  1230 + }
  1231 + conn_set_state(c, conn_mwrite);
  1232 + /* Remember this command so we can garbage collect it later */
  1233 + c->item = it;
  1234 + } else {
  1235 + pthread_mutex_lock(&c->thread->stats.mutex);
  1236 + c->thread->stats.touch_cmds++;
  1237 + c->thread->stats.touch_misses++;
  1238 + pthread_mutex_unlock(&c->thread->stats.mutex);
  1239 +
  1240 + MEMCACHED_COMMAND_TOUCH(c->sfd, key, nkey, -1, 0);
  1241 +
  1242 + if (c->noreply) {
  1243 + conn_set_state(c, conn_new_cmd);
  1244 + } else {
  1245 + write_bin_error(c, PROTOCOL_BINARY_RESPONSE_KEY_ENOENT, 0);
  1246 + }
  1247 + }
  1248 +
  1249 + if (settings.detail_enabled) {
  1250 + stats_prefix_record_get(key, nkey, NULL != it);
  1251 + }
  1252 +}
  1253 +
1181 1254 static void process_bin_get(conn *c) {
1182 1255 item *it;
1183 1256
@@ -1736,6 +1809,9 @@ static void dispatch_bin_command(conn *c) {
1736 1809 case PROTOCOL_BINARY_CMD_GETKQ:
1737 1810 c->cmd = PROTOCOL_BINARY_CMD_GETK;
1738 1811 break;
  1812 + case PROTOCOL_BINARY_CMD_GATQ:
  1813 + c->cmd = PROTOCOL_BINARY_CMD_GATQ;
  1814 + break;
1739 1815 default:
1740 1816 c->noreply = false;
1741 1817 }
@@ -1837,6 +1913,15 @@ static void dispatch_bin_command(conn *c) {
1837 1913 protocol_error = 1;
1838 1914 }
1839 1915 break;
  1916 + case PROTOCOL_BINARY_CMD_TOUCH:
  1917 + case PROTOCOL_BINARY_CMD_GAT:
  1918 + case PROTOCOL_BINARY_CMD_GATQ:
  1919 + if (extlen == 4 && keylen != 0) {
  1920 + bin_read_key(c, bin_reading_touch_key, 4);
  1921 + } else {
  1922 + protocol_error = 1;
  1923 + }
  1924 + break;
1840 1925 default:
1841 1926 write_bin_error(c, PROTOCOL_BINARY_RESPONSE_UNKNOWN_COMMAND, bodylen);
1842 1927 }
@@ -2064,6 +2149,9 @@ static void complete_nread_binary(conn *c) {
2064 2149 case bin_reading_get_key:
2065 2150 process_bin_get(c);
2066 2151 break;
  2152 + case bin_reading_touch_key:
  2153 + process_bin_touch(c);
  2154 + break;
2067 2155 case bin_reading_stat:
2068 2156 process_bin_stat(c);
2069 2157 break;
@@ -2413,6 +2501,7 @@ static void server_stats(ADD_STAT add_stats, conn *c) {
2413 2501 APPEND_STAT("cmd_get", "%llu", (unsigned long long)thread_stats.get_cmds);
2414 2502 APPEND_STAT("cmd_set", "%llu", (unsigned long long)slab_stats.set_cmds);
2415 2503 APPEND_STAT("cmd_flush", "%llu", (unsigned long long)thread_stats.flush_cmds);
  2504 + APPEND_STAT("cmd_touch", "%llu", (unsigned long long)thread_stats.touch_cmds);
2416 2505 APPEND_STAT("get_hits", "%llu", (unsigned long long)slab_stats.get_hits);
2417 2506 APPEND_STAT("get_misses", "%llu", (unsigned long long)thread_stats.get_misses);
2418 2507 APPEND_STAT("delete_misses", "%llu", (unsigned long long)thread_stats.delete_misses);
@@ -2424,6 +2513,8 @@ static void server_stats(ADD_STAT add_stats, conn *c) {
2424 2513 APPEND_STAT("cas_misses", "%llu", (unsigned long long)thread_stats.cas_misses);
2425 2514 APPEND_STAT("cas_hits", "%llu", (unsigned long long)slab_stats.cas_hits);
2426 2515 APPEND_STAT("cas_badval", "%llu", (unsigned long long)slab_stats.cas_badval);
  2516 + APPEND_STAT("touch_hits", "%llu", (unsigned long long)slab_stats.touch_hits);
  2517 + APPEND_STAT("touch_misses", "%llu", (unsigned long long)thread_stats.touch_misses);
2427 2518 APPEND_STAT("auth_cmds", "%llu", (unsigned long long)thread_stats.auth_cmds);
2428 2519 APPEND_STAT("auth_errors", "%llu", (unsigned long long)thread_stats.auth_errors);
2429 2520 APPEND_STAT("bytes_read", "%llu", (unsigned long long)thread_stats.bytes_read);
10 memcached.h
@@ -162,7 +162,8 @@ enum bin_substates {
162 162 bin_reading_incr_header,
163 163 bin_read_flush_exptime,
164 164 bin_reading_sasl_auth,
165   - bin_reading_sasl_auth_data
  165 + bin_reading_sasl_auth_data,
  166 + bin_reading_touch_key,
166 167 };
167 168
168 169 enum protocol {
@@ -201,6 +202,7 @@ typedef unsigned int rel_time_t;
201 202 struct slab_stats {
202 203 uint64_t set_cmds;
203 204 uint64_t get_hits;
  205 + uint64_t touch_hits;
204 206 uint64_t delete_hits;
205 207 uint64_t cas_hits;
206 208 uint64_t cas_badval;
@@ -215,6 +217,8 @@ struct thread_stats {
215 217 pthread_mutex_t mutex;
216 218 uint64_t get_cmds;
217 219 uint64_t get_misses;
  220 + uint64_t touch_cmds;
  221 + uint64_t touch_misses;
218 222 uint64_t delete_misses;
219 223 uint64_t incr_misses;
220 224 uint64_t decr_misses;
@@ -241,8 +245,11 @@ struct stats {
241 245 unsigned int conn_structs;
242 246 uint64_t get_cmds;
243 247 uint64_t set_cmds;
  248 + uint64_t touch_cmds;
244 249 uint64_t get_hits;
245 250 uint64_t get_misses;
  251 + uint64_t touch_hits;
  252 + uint64_t touch_misses;
246 253 uint64_t evictions;
247 254 uint64_t reclaimed;
248 255 time_t started; /* when the process was started */
@@ -476,6 +483,7 @@ item *item_alloc(char *key, size_t nkey, int flags, rel_time_t exptime, int nbyt
476 483 char *item_cachedump(const unsigned int slabs_clsid, const unsigned int limit, unsigned int *bytes);
477 484 void item_flush_expired(void);
478 485 item *item_get(const char *key, const size_t nkey);
  486 +item *item_touch(const char *key, const size_t nkey, uint32_t exptime);
479 487 int item_link(item *it);
480 488 void item_remove(item *it);
481 489 int item_replace(item *it, item *new_it);
42 protocol_binary.h
@@ -105,6 +105,9 @@ extern "C"
105 105 PROTOCOL_BINARY_CMD_FLUSHQ = 0x18,
106 106 PROTOCOL_BINARY_CMD_APPENDQ = 0x19,
107 107 PROTOCOL_BINARY_CMD_PREPENDQ = 0x1a,
  108 + PROTOCOL_BINARY_CMD_TOUCH = 0x1c,
  109 + PROTOCOL_BINARY_CMD_GAT = 0x1d,
  110 + PROTOCOL_BINARY_CMD_GATQ = 0x1e,
108 111
109 112 PROTOCOL_BINARY_CMD_SASL_LIST_MECHS = 0x20,
110 113 PROTOCOL_BINARY_CMD_SASL_AUTH = 0x21,
@@ -382,6 +385,45 @@ extern "C"
382 385 typedef protocol_binary_response_no_extras protocol_binary_response_stats;
383 386
384 387 /**
  388 + * Definition of the packet used by the touch command.
  389 + */
  390 + typedef union {
  391 + struct {
  392 + protocol_binary_request_header header;
  393 + struct {
  394 + uint32_t expiration;
  395 + } body;
  396 + } message;
  397 + uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
  398 + } protocol_binary_request_touch;
  399 +
  400 + /**
  401 + * Definition of the packet returned from the touch command
  402 + */
  403 + typedef protocol_binary_response_no_extras protocol_binary_response_touch;
  404 +
  405 + /**
  406 + * Definition of the packet used by the GAT(Q) command.
  407 + */
  408 + typedef union {
  409 + struct {
  410 + protocol_binary_request_header header;
  411 + struct {
  412 + uint32_t expiration;
  413 + } body;
  414 + } message;
  415 + uint8_t bytes[sizeof(protocol_binary_request_header) + 4];
  416 + } protocol_binary_request_gat;
  417 +
  418 + typedef protocol_binary_request_gat protocol_binary_request_gatq;
  419 +
  420 + /**
  421 + * Definition of the packet returned from the GAT(Q)
  422 + */
  423 + typedef protocol_binary_response_get protocol_binary_response_gat;
  424 + typedef protocol_binary_response_get protocol_binary_response_gatq;
  425 +
  426 + /**
385 427 * Definition of a request for a range operation.
386 428 * See http://code.google.com/p/memcached/wiki/RangeOps
387 429 *
3  slabs.c
@@ -377,7 +377,8 @@ static void do_slabs_stats(ADD_STAT add_stats, void *c) {
377 377 (unsigned long long)thread_stats.slab_stats[i].cas_hits);
378 378 APPEND_NUM_STAT(i, "cas_badval", "%llu",
379 379 (unsigned long long)thread_stats.slab_stats[i].cas_badval);
380   -
  380 + APPEND_NUM_STAT(i, "touch_hits", "%llu",
  381 + (unsigned long long)thread_stats.slab_stats[i].touch_hits);
381 382 total++;
382 383 }
383 384 }
43 t/binary.t
@@ -2,7 +2,7 @@
2 2
3 3 use strict;
4 4 use warnings;
5   -use Test::More tests => 3376;
  5 +use Test::More tests => 3435;
6 6 use FindBin qw($Bin);
7 7 use lib "$Bin/lib";
8 8 use MemcachedTest;
@@ -41,6 +41,9 @@ use constant CMD_QUITQ => 0x17;
41 41 use constant CMD_FLUSHQ => 0x18;
42 42 use constant CMD_APPENDQ => 0x19;
43 43 use constant CMD_PREPENDQ => 0x1A;
  44 +use constant CMD_TOUCH => 0x1C;
  45 +use constant CMD_GAT => 0x1D;
  46 +use constant CMD_GATQ => 0x1E;
44 47
45 48 # REQ and RES formats are divided even though they currently share
46 49 # the same format, since they _could_ differ in the future.
@@ -237,6 +240,23 @@ is($mc->decr("x", 211), 0, "Floor is zero");
237 240 }
238 241 }
239 242
  243 +# diag "Touch commands";
  244 +{
  245 + $mc->flush;
  246 + $mc->set("totouch", "toast", 0, 1);
  247 + my $res = $mc->touch("totouch", 10);
  248 + sleep 2;
  249 + $check->("totouch", 0, "toast");
  250 +
  251 + $mc->set("totouch", "toast2", 0, 1);
  252 + my ($flags, $val, $i) = $mc->gat("totouch", 10);
  253 + is($val, "toast2", "GAT returned correct value");
  254 + sleep 2;
  255 + $check->("totouch", 0, "toast2");
  256 +
  257 + # Test miss as well
  258 +}
  259 +
240 260 # diag "Silent set.";
241 261 $mc->silent_mutation(::CMD_SETQ, 'silentset', 'silentsetval');
242 262
@@ -681,6 +701,27 @@ sub get_multi {
681 701 return \%return;
682 702 }
683 703
  704 +sub touch {
  705 + my $self = shift;
  706 + my ($key, $expire) = @_;
  707 + my $extra_header = pack "N", $expire;
  708 + my $cas = 0;
  709 + return $self->_do_command(::CMD_TOUCH, $key, '', $extra_header, $cas);
  710 +}
  711 +
  712 +sub gat {
  713 + my $self = shift;
  714 + my $key = shift;
  715 + my $expire = shift;
  716 + my $extra_header = pack "N", $expire;
  717 + my ($rv, $cas) = $self->_do_command(::CMD_GAT, $key, '', $extra_header);
  718 +
  719 + my $header = substr $rv, 0, 4, '';
  720 + my $flags = unpack("N", $header);
  721 +
  722 + return ($flags, $rv, $cas);
  723 +}
  724 +
684 725 sub version {
685 726 my $self = shift;
686 727 return $self->_do_command(::CMD_VERSION, '', '');
2  t/stats.t
@@ -58,7 +58,7 @@ my $sock = $server->sock;
58 58 my $stats = mem_stats($sock);
59 59
60 60 # Test number of keys
61   -is(scalar(keys(%$stats)), 39, "39 stats values");
  61 +is(scalar(keys(%$stats)), 42, "42 stats values");
62 62
63 63 # Test initial state
64 64 foreach my $key (qw(curr_items total_items bytes cmd_get cmd_set get_hits evictions get_misses
34 testapp.c
@@ -823,6 +823,33 @@ static off_t flush_command(char* buf, size_t bufsz, uint8_t cmd, uint32_t exptim
823 823 return size;
824 824 }
825 825
  826 +
  827 +static off_t touch_command(char* buf,
  828 + size_t bufsz,
  829 + uint8_t cmd,
  830 + const void* key,
  831 + size_t keylen,
  832 + uint32_t exptime) {
  833 + protocol_binary_request_touch *request = (void*)buf;
  834 + assert(bufsz > sizeof(*request));
  835 +
  836 + memset(request, 0, sizeof(*request));
  837 + request->message.header.request.magic = PROTOCOL_BINARY_REQ;
  838 + request->message.header.request.opcode = cmd;
  839 +
  840 + request->message.header.request.keylen = htons(keylen);
  841 + request->message.header.request.extlen = 4;
  842 + request->message.body.expiration = htonl(exptime);
  843 + request->message.header.request.bodylen = htonl(keylen + 4);
  844 +
  845 + request->message.header.request.opaque = 0xdeadbeef;
  846 +
  847 + off_t key_offset = sizeof(protocol_binary_request_no_extras) + 4;
  848 +
  849 + memcpy(buf + key_offset, key, keylen);
  850 + return sizeof(protocol_binary_request_no_extras) + 4 + keylen;
  851 +}
  852 +
826 853 static off_t arithmetic_command(char* buf,
827 854 size_t bufsz,
828 855 uint8_t cmd,
@@ -1674,6 +1701,13 @@ static enum test_return test_binary_pipeline_hickup_chunk(void *buffer, size_t b
1674 1701 key, keylen, NULL, 0);
1675 1702 break;
1676 1703
  1704 + case PROTOCOL_BINARY_CMD_TOUCH:
  1705 + case PROTOCOL_BINARY_CMD_GAT:
  1706 + case PROTOCOL_BINARY_CMD_GATQ:
  1707 + len = touch_command(command.bytes, sizeof(command.bytes), cmd,
  1708 + key, keylen, 10);
  1709 + break;
  1710 +
1677 1711 case PROTOCOL_BINARY_CMD_STAT:
1678 1712 len = raw_command(command.bytes, sizeof(command.bytes),
1679 1713 PROTOCOL_BINARY_CMD_STAT,
17 thread.c
@@ -346,6 +346,14 @@ item *item_get(const char *key, const size_t nkey) {
346 346 return it;
347 347 }
348 348
  349 +item *item_touch(const char *key, size_t nkey, uint32_t exptime) {
  350 + item *it;
  351 + pthread_mutex_lock(&cache_lock);
  352 + it = do_item_touch(key, nkey, exptime);
  353 + pthread_mutex_unlock(&cache_lock);
  354 + return it;
  355 +}
  356 +
349 357 /*
350 358 * Links an item into the LRU and hashtable.
351 359 */
@@ -478,6 +486,8 @@ void threadlocal_stats_reset(void) {
478 486
479 487 threads[ii].stats.get_cmds = 0;
480 488 threads[ii].stats.get_misses = 0;
  489 + threads[ii].stats.touch_cmds = 0;
  490 + threads[ii].stats.touch_misses = 0;
481 491 threads[ii].stats.delete_misses = 0;
482 492 threads[ii].stats.incr_misses = 0;
483 493 threads[ii].stats.decr_misses = 0;
@@ -492,6 +502,7 @@ void threadlocal_stats_reset(void) {
492 502 for(sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
493 503 threads[ii].stats.slab_stats[sid].set_cmds = 0;
494 504 threads[ii].stats.slab_stats[sid].get_hits = 0;
  505 + threads[ii].stats.slab_stats[sid].touch_hits = 0;
495 506 threads[ii].stats.slab_stats[sid].delete_hits = 0;
496 507 threads[ii].stats.slab_stats[sid].incr_hits = 0;
497 508 threads[ii].stats.slab_stats[sid].decr_hits = 0;
@@ -515,6 +526,8 @@ void threadlocal_stats_aggregate(struct thread_stats *stats) {
515 526
516 527 stats->get_cmds += threads[ii].stats.get_cmds;
517 528 stats->get_misses += threads[ii].stats.get_misses;
  529 + stats->touch_cmds += threads[ii].stats.touch_cmds;
  530 + stats->touch_misses += threads[ii].stats.touch_misses;
518 531 stats->delete_misses += threads[ii].stats.delete_misses;
519 532 stats->decr_misses += threads[ii].stats.decr_misses;
520 533 stats->incr_misses += threads[ii].stats.incr_misses;
@@ -531,6 +544,8 @@ void threadlocal_stats_aggregate(struct thread_stats *stats) {
531 544 threads[ii].stats.slab_stats[sid].set_cmds;
532 545 stats->slab_stats[sid].get_hits +=
533 546 threads[ii].stats.slab_stats[sid].get_hits;
  547 + stats->slab_stats[sid].touch_hits +=
  548 + threads[ii].stats.slab_stats[sid].touch_hits;
534 549 stats->slab_stats[sid].delete_hits +=
535 550 threads[ii].stats.slab_stats[sid].delete_hits;
536 551 stats->slab_stats[sid].decr_hits +=
@@ -552,6 +567,7 @@ void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out) {
552 567
553 568 out->set_cmds = 0;
554 569 out->get_hits = 0;
  570 + out->touch_hits = 0;
555 571 out->delete_hits = 0;
556 572 out->incr_hits = 0;
557 573 out->decr_hits = 0;
@@ -561,6 +577,7 @@ void slab_stats_aggregate(struct thread_stats *stats, struct slab_stats *out) {
561 577 for (sid = 0; sid < MAX_NUMBER_OF_SLAB_CLASSES; sid++) {
562 578 out->set_cmds += stats->slab_stats[sid].set_cmds;
563 579 out->get_hits += stats->slab_stats[sid].get_hits;
  580 + out->touch_hits += stats->slab_stats[sid].touch_hits;
564 581 out->delete_hits += stats->slab_stats[sid].delete_hits;
565 582 out->decr_hits += stats->slab_stats[sid].decr_hits;
566 583 out->incr_hits += stats->slab_stats[sid].incr_hits;
2  trace.h
@@ -22,6 +22,8 @@
22 22 #define MEMCACHED_COMMAND_DELETE_ENABLED() (0)
23 23 #define MEMCACHED_COMMAND_GET(arg0, arg1, arg2, arg3, arg4)
24 24 #define MEMCACHED_COMMAND_GET_ENABLED() (0)
  25 +#define MEMCACHED_COMMAND_TOUCH(arg0, arg1, arg2, arg3, arg4)
  26 +#define MEMCACHED_COMMAND_TOUCH_ENABLED() (0)
25 27 #define MEMCACHED_COMMAND_INCR(arg0, arg1, arg2, arg3)
26 28 #define MEMCACHED_COMMAND_INCR_ENABLED() (0)
27 29 #define MEMCACHED_COMMAND_PREPEND(arg0, arg1, arg2, arg3, arg4)

0 comments on commit d87f568

Please sign in to comment.
Something went wrong with that request. Please try again.