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