2
* Copyright 2013 Google Inc.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
17
// Author: jmarantz@google.com (Joshua Marantz)
19
#ifndef PAGESPEED_KERNEL_CACHE_PURGE_CONTEXT_H_
20
#define PAGESPEED_KERNEL_CACHE_PURGE_CONTEXT_H_
24
#include "pagespeed/kernel/base/basictypes.h"
25
#include "pagespeed/kernel/base/callback.h"
26
#include "pagespeed/kernel/base/scoped_ptr.h"
27
#include "pagespeed/kernel/base/string.h"
28
#include "pagespeed/kernel/base/string_util.h"
29
#include "pagespeed/kernel/base/timer.h"
30
#include "pagespeed/kernel/cache/purge_set.h"
31
#include "pagespeed/kernel/util/copy_on_write.h"
33
namespace net_instaweb {
39
class NamedLockManager;
46
// Handles purging of URLs, atomically persisting them to disk, allowing
47
// multiple concurrent threads/processes to handle purge requests and
48
// propogate them to the other processes.
50
// All public methods in this class are thread-safe.
52
// This class depends on Statistics being functional. If statistics are off,
53
// then cache purging may be slower, but it will still work.
56
typedef Callback2<bool, StringPiece> PurgeCallback;
57
typedef Callback1<const CopyOnWrite<PurgeSet>&> PurgeSetCallback;
59
// The source-of-truth of the purge data is kept in files. These files
60
// are checked for changes via stat every 5 seconds.
62
// TODO(jmarantz): make this settable.
63
static const int kCheckCacheIntervalMs = 5 * Timer::kSecondMs;
66
static const char kCancellations[];
67
static const char kContentions[];
68
static const char kFileParseFailures[];
69
static const char kFileStats[];
70
static const char kFileWriteFailures[];
71
static const char kFileWrites[];
72
static const char kPurgeIndex[];
73
static const char kPurgePollTimestampMs[];
74
static const char kStatCalls[];
76
PurgeContext(StringPiece filename,
77
FileSystem* file_system,
79
int max_bytes_in_cache,
80
ThreadSystem* thread_system,
81
NamedLockManager* lock_manager,
83
Statistics* statistics,
84
MessageHandler* handler);
87
static void InitStats(Statistics* statistics);
89
// By default, PurgeContext will try to acquire the lock and write the
90
// cache.purge file as soon as it is called. This may present a significant
91
// load to the file system, causing delays.
93
// In a multi-threaded or asynchronous environment (e.g. any environment
94
// other than Apache HTTPD pre-fork MPM), it is desirable to batch up
95
// requests for a time (e.g. 1 second) before writing the updated cache file.
96
void set_request_batching_delay_ms(int64 delay_ms) {
97
request_batching_delay_ms_ = delay_ms;
100
// Adds a URL to the purge-set, atomically updating the purge
101
// file, and transmitting the results to the system, attempting
102
// to do so within a bounded amount of time.
104
// Calls callback(true, "") if the invalidation succeeded (was able to grab
105
// the global lock in a finite amount of time), and callback(false, "reason")
106
// if it failed. If we fail to take the lock then the invalidation is dropped
107
// on the floor, and statistics "purge_cancellations" is bumped.
108
void AddPurgeUrl(StringPiece url, int64 timestamp_ms,
109
PurgeCallback* callback);
111
// Sets the global invalidation timestamp.
113
// Calls callback(true, "") if the invalidation succeeded (was able to grab
114
// the global lock in a finite amount of time), and callback(false, "reason")
115
// if it failed. If we fail to take the lock then the invalidation is dropped
116
// on the floor, and statistics "purge_cancellations" is bumped.
117
void SetCachePurgeGlobalTimestampMs(int64 timestamp_ms,
118
PurgeCallback* callback);
120
// Periodically updates the purge-set from disk if enough time has
121
// expired. This can be called on every request.
122
void PollFileSystem();
124
// To test whether URLs are purged, you must capture a PurgeSet by registering
125
// a callback. The PurgeSet is passed to the callback using a CopyOnWrite
126
// wrapper so that the callback can efficiently share the storage.
128
// IsValid calls can then be made using the PurgeSet captured by the callback.
129
void SetUpdateCallback(PurgeSetCallback* cb);
131
// Indicates whether individual URL purging is supported. If false,
132
// then we only take the cache.flush file timestamp to do full cache
133
// flushes. If true, then we read and parse the contents of the file
134
// to find the global invalidation time and cache-flush times for
135
// the individual entries.
136
void set_enable_purge(bool x) { enable_purge_ = x; }
139
friend class PurgeContextTest;
141
typedef std::vector<PurgeCallback*> PurgeCallbackVector;
143
// Having acquired the lock, merges all sources of purge information and
144
// write the purge file. This must be called with interprocess_lock_
146
void UpdateCachePurgeFile();
148
// Reads the contents of filename_ into *purges_from_file. Any invalid
149
// data in the file is ignored.
151
// This method doesn't read or write dynamic data from the class other
152
// than statistics, so it's thread-safe. Also it does not need to be
153
// called with interprocess_lock_ held as the writes are made atomic
154
// via write-to-temp + rename.
155
void ReadPurgeFile(PurgeSet* purges_from_file);
156
void ReadFileAndCallCallbackIfChanged(bool needs_update);
158
// Combines the purges_from_file with pending_purges_ and purge_set_,
159
// serializes the result into *buffer for writing back to the file.
161
// Note: this does *not* update purge_set_ (it treats it as read-only).
162
// However, it bumps purge_index_ to induce a file-read on
163
// the next call to PollFileSystem().
165
// This helper method is used as part of a read/modify/write/verify
166
// sequence, and it returns the callbacks that must be called when
167
// that sequence is done. Similarly, this method transfers
168
// num_consecutive_failures_ into *failures, zeroing the former.
170
// return_purges gets populated with pending_purges_ via swap. It
171
// can be used to replace pending_purges_ in the event of a write
172
// failure so we don't lose that data.
174
// return_callbacks contains the callbacks to call when the
175
// transaction is complete.
177
// TODO(jmarantz): the return values from this method comprise a
178
// transaction which should be made an explicit class or struct.
180
// This method is thread-safe; it grabs mutex_.
181
void ModifyPurgeSet(PurgeSet* purges_from_file, GoogleString* buffer,
182
PurgeCallbackVector* return_callbacks,
183
PurgeSet* return_purges,
186
// When a write fails, we must do one of these:
187
// a) restore the pending purges & callbacks and try to re-take the lock.
188
// b) call the callbacks with 'false' and drop return_purges.
189
void HandleWriteFailure(int failures,
190
PurgeCallbackVector* callbacks,
191
PurgeSet* return_purges,
192
bool* lock_and_update);
194
// Writes the serialized purge data into filename_. This method must
195
// be called with the interprocess_lock_ held, but does not reference
196
// or update dynamic data in this.
197
bool WritePurgeFile(const GoogleString& buffer);
199
// Returns true if the contents of filename_ matches the specified buffer.
200
bool Verify(const GoogleString& expected_purge_file_contents);
202
// Returns the name used to create a new lock. Visible for testing
203
// to aid in testing lock contention.
204
GoogleString LockName() const { return StrCat(filename_, "-lock"); }
206
// Initiates a scheduler-alarm to call GrabLockAndUpdate after a
207
// small delay. The delay is used to batch bursts of cache-purge
208
// file updates, thereby rate-limiting disk-writes.
209
void WaitForTimerAndGrabLock();
211
// Attempts to grab a lock for the cache-purge file, calling
212
// UpdateCachePurgeFile if successful, and CancelCachePurgeFile
213
// if we failed to grab the lock.
214
void GrabLockAndUpdate();
216
// There is a non-zero chance that this thread will be unable to acquire the
217
// named-lock in the given time-limit. When this occurs, we'll fail the
218
// requests, calling the callbacks with 'false'.
219
void CancelCachePurgeFile();
221
// Ensures a timestamp has reasonable syntax and is not (too far) in the
224
// The parsed timestamp is placed in *timestamp_ms, and true/false is
225
// used to indicate parsing & validation success.
226
bool ParseAndValidateTimestamp(StringPiece time_string, int64 now_ms,
227
int64* timestamp_ms);
229
GoogleString filename_;
230
scoped_ptr<NamedLock> interprocess_lock_;
231
FileSystem* file_system_;
234
Statistics* statistics_;
235
scoped_ptr<AbstractMutex> mutex_;
236
CopyOnWrite<PurgeSet> purge_set_; // protected by mutex_
237
PurgeSet pending_purges_; // protected by mutex_
238
PurgeCallbackVector pending_callbacks_; // protected by mutex_
239
int64 local_purge_index_; // protected by mutex_
240
int num_consecutive_failures_; // protected_by mutex_
241
bool waiting_for_interprocess_lock_; // protected_by mutex_
242
bool reading_; // protected_by mutex_
244
bool enable_purge_; // When false, can only flush entire cache.
245
int max_bytes_in_cache_;
247
int64 request_batching_delay_ms_;
249
Variable* cancellations_;
250
Variable* contentions_;
251
Variable* file_parse_failures_;
252
Variable* file_stats_;
253
Variable* file_write_failures_;
254
Variable* file_writes_;
255
Variable* purge_index_;
256
scoped_ptr<UpDownCounter> purge_poll_timestamp_ms_;
258
Scheduler* scheduler_;
259
MessageHandler* message_handler_;
261
scoped_ptr<PurgeSetCallback> update_callback_;
263
DISALLOW_COPY_AND_ASSIGN(PurgeContext);
266
} // namespace net_instaweb
268
#endif // PAGESPEED_KERNEL_CACHE_PURGE_CONTEXT_H_