1 /** @file
2
3 A brief file description
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 /****************************************************************************
25
26 HttpCacheSM.cc
27
28 Description:
29
30
31 ****************************************************************************/
32
33 #include "HttpCacheSM.h"
34 #include "HttpSM.h"
35 #include "HttpDebugNames.h"
36
37 #define SM_REMEMBER(sm, e, r) \
38 { \
39 sm->history.push_back(MakeSourceLocation(), e, r); \
40 }
41
42 #define STATE_ENTER(state_name, event) \
43 { \
44 SM_REMEMBER(master_sm, event, NO_REENTRANT); \
45 Debug("http_cache", "[%" PRId64 "] [%s, %s]", master_sm->sm_id, #state_name, HttpDebugNames::get_event_name(event)); \
46 }
47
HttpCacheAction()48 HttpCacheAction::HttpCacheAction() {}
49
50 void
cancel(Continuation * c)51 HttpCacheAction::cancel(Continuation *c)
52 {
53 ink_assert(c == nullptr || c == sm->master_sm);
54 ink_assert(this->cancelled == 0);
55
56 this->cancelled = 1;
57 if (sm->pending_action) {
58 sm->pending_action->cancel();
59 }
60 }
61
HttpCacheSM()62 HttpCacheSM::HttpCacheSM()
63 : Continuation(nullptr),
64
65 captive_action()
66
67 {
68 }
69
70 //////////////////////////////////////////////////////////////////////////
71 //
72 // HttpCacheSM::state_cache_open_read()
73 //
74 // State the cache calls back the state machine into on a open_read
75 // call. The set of allowed events (from the cache) are:
76 // - CACHE_EVENT_OPEN_READ
77 // - document matching request is in the cache
78 // - CACHE_EVENT_OPEN_READ_FAILED
79 // if (data != ECACHEDOCBUSY)
80 // - document matching request is not in the cache
81 // if (data == ECACHEDOCBUSY)
82 // - document with same URL as request is in the cache
83 // but is being currently updated by another state
84 // machine. Keep in mind that the document may NOT
85 // match any of the request headers - it just matches
86 // the URL. In other words, the document that is being
87 // written to by another state machine may be an
88 // alternate of the document the request wants.
89 // - EVENT_INTERVAL
90 // - a previous open_read returned "failed_in_progress". we
91 // decided to retry the open read. we scheduled the event
92 // processor to call us back after n msecs so that we can
93 // reissue the open_read. this is the call from the event
94 // processor.
95 //
96 //////////////////////////////////////////////////////////////////////////
97 int
state_cache_open_read(int event,void * data)98 HttpCacheSM::state_cache_open_read(int event, void *data)
99 {
100 STATE_ENTER(&HttpCacheSM::state_cache_open_read, event);
101 ink_assert(captive_action.cancelled == 0);
102 pending_action = nullptr;
103
104 if (captive_action.cancelled == 1) {
105 return VC_EVENT_CONT; // SM gave up on us
106 }
107
108 switch (event) {
109 case CACHE_EVENT_OPEN_READ:
110 HTTP_INCREMENT_DYN_STAT(http_current_cache_connections_stat);
111 ink_assert((cache_read_vc == nullptr) || master_sm->t_state.redirect_info.redirect_in_process);
112 if (cache_read_vc) {
113 // redirect follow in progress, close the previous cache_read_vc
114 close_read();
115 }
116 open_read_cb = true;
117 cache_read_vc = static_cast<CacheVConnection *>(data);
118 master_sm->handleEvent(event, &captive_action);
119 break;
120
121 case CACHE_EVENT_OPEN_READ_FAILED:
122 err_code = reinterpret_cast<intptr_t>(data);
123 if ((intptr_t)data == -ECACHE_DOC_BUSY) {
124 // Somebody else is writing the object
125 if (open_read_tries <= master_sm->t_state.txn_conf->max_cache_open_read_retries) {
126 // Retry to read; maybe the update finishes in time
127 open_read_cb = false;
128 do_schedule_in();
129 } else {
130 // Give up; the update didn't finish in time
131 // HttpSM will inform HttpTransact to 'proxy-only'
132 open_read_cb = true;
133 master_sm->handleEvent(event, &captive_action);
134 }
135 } else {
136 // Simple miss in the cache.
137 open_read_cb = true;
138 master_sm->handleEvent(event, &captive_action);
139 }
140 break;
141
142 case EVENT_INTERVAL:
143 // Retry the cache open read if the number retries is less
144 // than or equal to the max number of open read retries,
145 // else treat as a cache miss.
146 ink_assert(open_read_tries <= master_sm->t_state.txn_conf->max_cache_open_read_retries || write_locked);
147 Debug("http_cache",
148 "[%" PRId64 "] [state_cache_open_read] cache open read failure %d. "
149 "retrying cache open read...",
150 master_sm->sm_id, open_read_tries);
151
152 do_cache_open_read(cache_key);
153 break;
154
155 default:
156 ink_assert(0);
157 }
158
159 return VC_EVENT_CONT;
160 }
161
162 int
state_cache_open_write(int event,void * data)163 HttpCacheSM::state_cache_open_write(int event, void *data)
164 {
165 STATE_ENTER(&HttpCacheSM::state_cache_open_write, event);
166 ink_assert(captive_action.cancelled == 0);
167 pending_action = nullptr;
168
169 if (captive_action.cancelled == 1) {
170 return VC_EVENT_CONT; // SM gave up on us
171 }
172 bool read_retry_on_write_fail = false;
173
174 switch (event) {
175 case CACHE_EVENT_OPEN_WRITE:
176 HTTP_INCREMENT_DYN_STAT(http_current_cache_connections_stat);
177 ink_assert(cache_write_vc == nullptr);
178 cache_write_vc = static_cast<CacheVConnection *>(data);
179 open_write_cb = true;
180 master_sm->handleEvent(event, &captive_action);
181 break;
182
183 case CACHE_EVENT_OPEN_WRITE_FAILED:
184 if (master_sm->t_state.txn_conf->cache_open_write_fail_action == CACHE_WL_FAIL_ACTION_READ_RETRY) {
185 // fall back to open_read_tries
186 // Note that when CACHE_WL_FAIL_ACTION_READ_RETRY is configured, max_cache_open_write_retries
187 // is automatically ignored. Make sure to not disable max_cache_open_read_retries
188 // with CACHE_WL_FAIL_ACTION_READ_RETRY as this results in proxy'ing to origin
189 // without write retries in both a cache miss or a cache refresh scenario.
190 if (open_write_tries <= master_sm->t_state.txn_conf->max_cache_open_write_retries) {
191 Debug("http_cache", "[%" PRId64 "] [state_cache_open_write] cache open write failure %d. read retry triggered",
192 master_sm->sm_id, open_write_tries);
193 if (master_sm->t_state.txn_conf->max_cache_open_read_retries <= 0) {
194 Debug("http_cache",
195 "[%" PRId64 "] [state_cache_open_write] invalid config, cache write fail set to"
196 " read retry, but, max_cache_open_read_retries is not enabled",
197 master_sm->sm_id);
198 }
199 open_read_tries = 0;
200 read_retry_on_write_fail = true;
201 // make sure it doesn't loop indefinitely
202 open_write_tries = master_sm->t_state.txn_conf->max_cache_open_write_retries + 1;
203 }
204 }
205 if (read_retry_on_write_fail || open_write_tries <= master_sm->t_state.txn_conf->max_cache_open_write_retries) {
206 // Retry open write;
207 open_write_cb = false;
208 do_schedule_in();
209 } else {
210 // The cache is hosed or full or something.
211 // Forward the failure to the main sm
212 Debug("http_cache",
213 "[%" PRId64 "] [state_cache_open_write] cache open write failure %d. "
214 "done retrying...",
215 master_sm->sm_id, open_write_tries);
216 open_write_cb = true;
217 err_code = reinterpret_cast<intptr_t>(data);
218 master_sm->handleEvent(event, &captive_action);
219 }
220 break;
221
222 case EVENT_INTERVAL:
223 if (master_sm->t_state.txn_conf->cache_open_write_fail_action == CACHE_WL_FAIL_ACTION_READ_RETRY) {
224 Debug("http_cache",
225 "[%" PRId64 "] [state_cache_open_write] cache open write failure %d. "
226 "falling back to read retry...",
227 master_sm->sm_id, open_write_tries);
228 open_read_cb = false;
229 master_sm->handleEvent(CACHE_EVENT_OPEN_READ, &captive_action);
230 } else {
231 Debug("http_cache",
232 "[%" PRId64 "] [state_cache_open_write] cache open write failure %d. "
233 "retrying cache open write...",
234 master_sm->sm_id, open_write_tries);
235
236 // Retry the cache open write if the number retries is less
237 // than or equal to the max number of open write retries
238 ink_assert(open_write_tries <= master_sm->t_state.txn_conf->max_cache_open_write_retries);
239 open_write(&cache_key, lookup_url, read_request_hdr, master_sm->t_state.cache_info.object_read,
240 static_cast<time_t>(
241 (master_sm->t_state.cache_control.pin_in_cache_for < 0) ? 0 : master_sm->t_state.cache_control.pin_in_cache_for),
242 retry_write, false);
243 }
244 break;
245
246 default:
247 ink_release_assert(0);
248 }
249
250 return VC_EVENT_CONT;
251 }
252
253 void
do_schedule_in()254 HttpCacheSM::do_schedule_in()
255 {
256 ink_assert(pending_action == nullptr);
257 Action *action_handle =
258 mutex->thread_holding->schedule_in(this, HRTIME_MSECONDS(master_sm->t_state.txn_conf->cache_open_read_retry_time));
259
260 if (action_handle != ACTION_RESULT_DONE) {
261 pending_action = action_handle;
262 }
263
264 return;
265 }
266
267 Action *
do_cache_open_read(const HttpCacheKey & key)268 HttpCacheSM::do_cache_open_read(const HttpCacheKey &key)
269 {
270 open_read_tries++;
271 ink_assert(pending_action == nullptr);
272 if (write_locked) {
273 open_read_cb = false;
274 } else {
275 ink_assert(open_read_cb == false);
276 }
277 // Initialising read-while-write-inprogress flag
278 this->readwhilewrite_inprogress = false;
279 Action *action_handle = cacheProcessor.open_read(this, &key, this->read_request_hdr, this->http_params, this->read_pin_in_cache);
280
281 if (action_handle != ACTION_RESULT_DONE) {
282 pending_action = action_handle;
283 }
284 // Check to see if we've already called the user back
285 // If we have then it's ACTION_RESULT_DONE, other wise
286 // return our captive action and ensure that we are actually
287 // doing something useful
288 if (open_read_cb == true) {
289 return ACTION_RESULT_DONE;
290 } else {
291 ink_assert(pending_action != nullptr || write_locked == true);
292 captive_action.cancelled = 0; // Make sure not cancelled before we hand it out
293 return &captive_action;
294 }
295 }
296
297 Action *
open_read(const HttpCacheKey * key,URL * url,HTTPHdr * hdr,const OverridableHttpConfigParams * params,time_t pin_in_cache)298 HttpCacheSM::open_read(const HttpCacheKey *key, URL *url, HTTPHdr *hdr, const OverridableHttpConfigParams *params,
299 time_t pin_in_cache)
300 {
301 Action *act_return;
302
303 cache_key = *key;
304 lookup_url = url;
305 read_request_hdr = hdr;
306 http_params = params;
307 read_pin_in_cache = pin_in_cache;
308 ink_assert(pending_action == nullptr);
309 SET_HANDLER(&HttpCacheSM::state_cache_open_read);
310
311 lookup_max_recursive++;
312 current_lookup_level++;
313 open_read_cb = false;
314 act_return = do_cache_open_read(cache_key);
315 // the following logic is based on the assumption that the second
316 // lookup won't happen if the HttpSM hasn't been called back for the
317 // first lookup
318 if (current_lookup_level == lookup_max_recursive) {
319 current_lookup_level--;
320 ink_assert(current_lookup_level >= 0);
321 if (current_lookup_level == 0) {
322 lookup_max_recursive = 0;
323 }
324 return act_return;
325 } else {
326 ink_assert(current_lookup_level < lookup_max_recursive);
327 current_lookup_level--;
328
329 if (current_lookup_level == 0) {
330 lookup_max_recursive = 0;
331 }
332
333 return ACTION_RESULT_DONE;
334 }
335 }
336
337 Action *
open_write(const HttpCacheKey * key,URL * url,HTTPHdr * request,CacheHTTPInfo * old_info,time_t pin_in_cache,bool retry,bool allow_multiple)338 HttpCacheSM::open_write(const HttpCacheKey *key, URL *url, HTTPHdr *request, CacheHTTPInfo *old_info, time_t pin_in_cache,
339 bool retry, bool allow_multiple)
340 {
341 SET_HANDLER(&HttpCacheSM::state_cache_open_write);
342 ink_assert(pending_action == nullptr);
343 ink_assert((cache_write_vc == nullptr) || master_sm->t_state.redirect_info.redirect_in_process);
344 // INKqa12119
345 open_write_cb = false;
346 open_write_tries++;
347 this->retry_write = retry;
348
349 // We should be writing the same document we did
350 // a lookup on
351 // this is no longer true for multiple cache lookup
352 // ink_assert(url == lookup_url || lookup_url == NULL);
353 ink_assert(request == read_request_hdr || read_request_hdr == nullptr);
354 this->lookup_url = url;
355 this->read_request_hdr = request;
356 cache_key = *key;
357
358 // Make sure we are not stuck in a loop where the write
359 // fails but the retry read succeeds causing to issue
360 // a new write (could happen on a very busy document
361 // that must be revalidated every time)
362 // Changed by YTS Team, yamsat Plugin
363 if (open_write_tries > master_sm->redirection_tries &&
364 open_write_tries > master_sm->t_state.txn_conf->max_cache_open_write_retries) {
365 err_code = -ECACHE_DOC_BUSY;
366 master_sm->handleEvent(CACHE_EVENT_OPEN_WRITE_FAILED, &captive_action);
367 return ACTION_RESULT_DONE;
368 }
369
370 Action *action_handle =
371 cacheProcessor.open_write(this, 0, key, request,
372 // INKqa11166
373 allow_multiple ? (CacheHTTPInfo *)CACHE_ALLOW_MULTIPLE_WRITES : old_info, pin_in_cache);
374
375 if (action_handle != ACTION_RESULT_DONE) {
376 pending_action = action_handle;
377 }
378 // Check to see if we've already called the user back
379 // If we have then it's ACTION_RESULT_DONE, other wise
380 // return our captive action and ensure that we are actually
381 // doing something useful
382 if (open_write_cb == true) {
383 return ACTION_RESULT_DONE;
384 } else {
385 ink_assert(pending_action != nullptr);
386 captive_action.cancelled = 0; // Make sure not cancelled before we hand it out
387 return &captive_action;
388 }
389 }
390