fix #76: get returns NOTFOUND on TIMEOUT
[awesomized/libmemcached] / src / libmemcached / fetch.cc
1 /*
2 +--------------------------------------------------------------------+
3 | libmemcached-awesome - C/C++ Client Library for memcached |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted under the terms of the BSD license. |
7 | You should have received a copy of the license in a bundled file |
8 | named LICENSE; in case you did not receive a copy you can review |
9 | the terms online at: https://opensource.org/licenses/BSD-3-Clause |
10 +--------------------------------------------------------------------+
11 | Copyright (c) 2006-2014 Brian Aker https://datadifferential.com/ |
12 | Copyright (c) 2020-2021 Michael Wallner https://awesome.co/ |
13 +--------------------------------------------------------------------+
14 */
15
16 #include "libmemcached/common.h"
17
18 char *memcached_fetch(memcached_st *shell, char *key, size_t *key_length, size_t *value_length,
19 uint32_t *flags, memcached_return_t *error) {
20 Memcached *ptr = memcached2Memcached(shell);
21 memcached_return_t unused;
22 if (error == NULL) {
23 error = &unused;
24 }
25
26 if (memcached_is_udp(ptr)) {
27 if (value_length) {
28 *value_length = 0;
29 }
30
31 if (key_length) {
32 *key_length = 0;
33 }
34
35 if (flags) {
36 *flags = 0;
37 }
38
39 if (key) {
40 *key = 0;
41 }
42
43 *error = MEMCACHED_NOT_SUPPORTED;
44 return NULL;
45 }
46
47 memcached_result_st *result_buffer = &ptr->result;
48 result_buffer = memcached_fetch_result(ptr, result_buffer, error);
49 if (result_buffer == NULL or memcached_failed(*error)) {
50 WATCHPOINT_ASSERT(result_buffer == NULL);
51 if (value_length) {
52 *value_length = 0;
53 }
54
55 if (key_length) {
56 *key_length = 0;
57 }
58
59 if (flags) {
60 *flags = 0;
61 }
62
63 if (key) {
64 *key = 0;
65 }
66
67 return NULL;
68 }
69
70 if (value_length) {
71 *value_length = memcached_string_length(&result_buffer->value);
72 }
73
74 if (key) {
75 if (result_buffer->key_length > MEMCACHED_MAX_KEY) {
76 *error = MEMCACHED_KEY_TOO_BIG;
77 if (value_length) {
78 *value_length = 0;
79 }
80
81 if (key_length) {
82 *key_length = 0;
83 }
84
85 if (flags) {
86 *flags = 0;
87 }
88
89 if (key) {
90 *key = 0;
91 }
92
93 return NULL;
94 }
95
96 strncpy(key, result_buffer->item_key,
97 result_buffer->key_length); // For the binary protocol we will cut off the key :(
98 if (key_length) {
99 *key_length = result_buffer->key_length;
100 }
101 }
102
103 if (flags) {
104 *flags = result_buffer->item_flags;
105 }
106
107 return memcached_string_take_value(&result_buffer->value);
108 }
109
110 memcached_result_st *memcached_fetch_result(memcached_st *ptr, memcached_result_st *result,
111 memcached_return_t *error) {
112 memcached_return_t unused;
113 if (error == NULL) {
114 error = &unused;
115 }
116
117 if (ptr == NULL) {
118 *error = MEMCACHED_INVALID_ARGUMENTS;
119 return NULL;
120 }
121
122 if (memcached_is_udp(ptr)) {
123 *error = MEMCACHED_NOT_SUPPORTED;
124 return NULL;
125 }
126
127 if (result == NULL) {
128 // If we have already initialized (ie it is in use) our internal, we
129 // create one.
130 if (memcached_is_initialized(&ptr->result)) {
131 if ((result = memcached_result_create(ptr, NULL)) == NULL) {
132 *error = MEMCACHED_MEMORY_ALLOCATION_FAILURE;
133 return NULL;
134 }
135 } else {
136 result = memcached_result_create(ptr, &ptr->result);
137 }
138 }
139
140 *error = MEMCACHED_MAXIMUM_RETURN; // We use this to see if we ever go into the loop
141 memcached_instance_st *server;
142 memcached_return_t read_ret = MEMCACHED_SUCCESS;
143 bool connection_failures = false;
144 bool timeouts = false;
145 while ((server = memcached_io_get_readable_server(ptr, read_ret))) {
146 char buffer[MEMCACHED_DEFAULT_COMMAND_SIZE];
147 *error = memcached_response(server, buffer, sizeof(buffer), result);
148
149 if (*error == MEMCACHED_IN_PROGRESS) {
150 continue;
151 } else if (*error == MEMCACHED_CONNECTION_FAILURE) {
152 connection_failures = true;
153 continue;
154 } else if (*error == MEMCACHED_TIMEOUT) {
155 timeouts = true;
156 } else if (*error == MEMCACHED_SUCCESS) {
157 result->count++;
158 return result;
159 } else if (*error == MEMCACHED_END) {
160 memcached_server_response_reset(server);
161 } else if (*error != MEMCACHED_NOTFOUND) {
162 break;
163 }
164 }
165
166 if (*error == MEMCACHED_NOTFOUND and result->count) {
167 *error = MEMCACHED_END;
168 } else if (*error == MEMCACHED_MAXIMUM_RETURN and result->count) {
169 *error = MEMCACHED_END;
170 } else if (*error == MEMCACHED_MAXIMUM_RETURN) // while() loop was never entered
171 {
172 *error = MEMCACHED_NOTFOUND;
173 } else if (connection_failures) {
174 /*
175 If we have a connection failure to some servers, the caller may
176 wish to treat that differently to getting a definitive NOT_FOUND
177 from all servers, so return MEMCACHED_CONNECTION_FAILURE to allow
178 that.
179 */
180 *error = MEMCACHED_CONNECTION_FAILURE;
181 } else if (timeouts) {
182 *error = MEMCACHED_TIMEOUT;
183 } else if (*error == MEMCACHED_SUCCESS) {
184 *error = MEMCACHED_END;
185 } else if (result->count == 0) {
186 *error = MEMCACHED_NOTFOUND;
187 }
188
189 /* We have completed reading data */
190 if (memcached_is_allocated(result)) {
191 memcached_result_free(result);
192 } else {
193 result->count = 0;
194 memcached_string_reset(&result->value);
195 }
196
197 return NULL;
198 }
199
200 memcached_return_t memcached_fetch_execute(memcached_st *shell, memcached_execute_fn *callback,
201 void *context, uint32_t number_of_callbacks) {
202 Memcached *ptr = memcached2Memcached(shell);
203 memcached_result_st *result = &ptr->result;
204 memcached_return_t rc;
205 bool some_errors = false;
206
207 while ((result = memcached_fetch_result(ptr, result, &rc))) {
208 if (memcached_failed(rc) and rc == MEMCACHED_NOTFOUND) {
209 continue;
210 } else if (memcached_failed(rc)) {
211 memcached_set_error(*ptr, rc, MEMCACHED_AT);
212 some_errors = true;
213 continue;
214 }
215
216 for (uint32_t x = 0; x < number_of_callbacks; x++) {
217 memcached_return_t ret = (*callback[x])(ptr, result, context);
218 if (memcached_failed(ret)) {
219 some_errors = true;
220 memcached_set_error(*ptr, ret, MEMCACHED_AT);
221 break;
222 }
223 }
224 }
225
226 if (some_errors) {
227 return MEMCACHED_SOME_ERRORS;
228 }
229
230 // If we were able to run all keys without issue we return
231 // MEMCACHED_SUCCESS
232 if (memcached_success(rc)) {
233 return MEMCACHED_SUCCESS;
234 }
235
236 return rc;
237 }