]>
Commit | Line | Data |
---|---|---|
9487bae9 AR |
1 | /* |
2 | * $Id$ | |
3 | * | |
4 | * DEBUG: section 20 Memory Cache | |
5 | * | |
6 | */ | |
7 | ||
8 | #include "config.h" | |
a4555399 | 9 | #include "base/RunnersRegistry.h" |
9487bae9 AR |
10 | #include "ipc/mem/Page.h" |
11 | #include "ipc/mem/Pages.h" | |
12 | #include "MemObject.h" | |
13 | #include "MemStore.h" | |
14 | #include "HttpReply.h" | |
15 | ||
a4555399 AR |
16 | /// shared memory segment path to use for MemStore maps |
17 | static const char *ShmLabel = "cache_mem"; | |
9487bae9 AR |
18 | |
19 | // XXX: support storage using more than one page per entry | |
20 | ||
a4555399 AR |
21 | void |
22 | MemStore::Init() | |
23 | { | |
24 | const int64_t entryLimit = EntryLimit(); | |
25 | if (entryLimit <= 0) | |
26 | return; // no memory cache configured or a misconfiguration | |
27 | ||
28 | MemStoreMap *map = new MemStoreMap(ShmLabel, entryLimit); | |
29 | delete map; // we just wanted to initialize shared memory segments | |
30 | } | |
9487bae9 | 31 | |
17cf0a47 | 32 | MemStore::MemStore(): map(NULL), theCurrentSize(0) |
9487bae9 AR |
33 | { |
34 | } | |
35 | ||
36 | MemStore::~MemStore() | |
37 | { | |
38 | delete map; | |
39 | } | |
40 | ||
41 | void | |
a4555399 AR |
42 | MemStore::init() { |
43 | const int64_t entryLimit = EntryLimit(); | |
44 | if (entryLimit <= 0) | |
45 | return; // no memory cache configured or a misconfiguration | |
46 | ||
af2fda07 DK |
47 | if (IamWorkerProcess()) { // XXX: coordinator should not create MemStore |
48 | const int64_t max_size = Store::Root().maxObjectSize(); | |
49 | if (max_size == -1) { | |
50 | debugs(20, DBG_IMPORTANT, "WARNING: disk-cache maximum object size " | |
51 | "is unlimited but mem-cache maximum object size is " << | |
8424f881 | 52 | Ipc::Mem::PageSize() / 1024.0 << " KB"); |
af2fda07 DK |
53 | } else if (max_size > maxObjectSize()) { |
54 | debugs(20, DBG_IMPORTANT, "WARNING: disk-cache maximum object size " | |
55 | "is too large for mem-cache: " << | |
8424f881 DK |
56 | Store::Root().maxObjectSize() / 1024.0 << " KB > " << |
57 | maxObjectSize() / 1024.0 << " KB"); | |
af2fda07 DK |
58 | } |
59 | } | |
60 | ||
a4555399 AR |
61 | map = new MemStoreMap(ShmLabel); |
62 | map->cleaner = this; | |
9487bae9 AR |
63 | } |
64 | ||
65 | void | |
c4e688b7 | 66 | MemStore::stat(StoreEntry &e) const |
9487bae9 | 67 | { |
c4e688b7 AR |
68 | storeAppendPrintf(&e, "\n\nShared Memory Cache\n"); |
69 | ||
70 | storeAppendPrintf(&e, "Maximum Size: %.0f KB\n", Config.memMaxSize/1024.0); | |
71 | ||
72 | if (map) { | |
73 | const int limit = map->entryLimit(); | |
74 | storeAppendPrintf(&e, "Maximum entries: %9d\n", limit); | |
75 | if (limit > 0) { | |
39c1e1d9 DK |
76 | storeAppendPrintf(&e, "Current entries: %"PRId64" %.2f%%\n", |
77 | currentCount(), (100.0 * currentCount() / limit)); | |
c4e688b7 AR |
78 | |
79 | if (limit < 100) { // XXX: otherwise too expensive to count | |
80 | Ipc::ReadWriteLockStats stats; | |
81 | map->updateStats(stats); | |
82 | stats.dump(e); | |
83 | } | |
84 | } | |
85 | } | |
9487bae9 AR |
86 | } |
87 | ||
88 | void | |
89 | MemStore::maintain() | |
90 | { | |
91 | } | |
92 | ||
93 | uint64_t | |
94 | MemStore::minSize() const | |
95 | { | |
96 | return 0; // XXX: irrelevant, but Store parent forces us to implement this | |
97 | } | |
98 | ||
99 | uint64_t | |
100 | MemStore::maxSize() const | |
101 | { | |
102 | return 0; // XXX: make configurable | |
103 | } | |
104 | ||
39c1e1d9 DK |
105 | uint64_t |
106 | MemStore::currentSize() const | |
107 | { | |
17cf0a47 | 108 | return theCurrentSize >> 10; |
39c1e1d9 DK |
109 | } |
110 | ||
111 | uint64_t | |
112 | MemStore::currentCount() const | |
113 | { | |
114 | return map ? map->entryCount() : 0; | |
115 | } | |
116 | ||
af2fda07 DK |
117 | int64_t |
118 | MemStore::maxObjectSize() const | |
119 | { | |
120 | return Ipc::Mem::PageSize(); | |
121 | } | |
122 | ||
9487bae9 AR |
123 | void |
124 | MemStore::updateSize(int64_t eSize, int sign) | |
125 | { | |
126 | // XXX: irrelevant, but Store parent forces us to implement this | |
127 | fatal("MemStore::updateSize should not be called"); | |
128 | } | |
129 | ||
130 | void | |
131 | MemStore::reference(StoreEntry &) | |
132 | { | |
133 | } | |
134 | ||
135 | void | |
136 | MemStore::dereference(StoreEntry &) | |
137 | { | |
138 | } | |
139 | ||
140 | int | |
141 | MemStore::callback() | |
142 | { | |
143 | return 0; | |
144 | } | |
145 | ||
146 | StoreSearch * | |
147 | MemStore::search(String const, HttpRequest *) | |
148 | { | |
149 | fatal("not implemented"); | |
150 | return NULL; | |
151 | } | |
152 | ||
153 | StoreEntry * | |
154 | MemStore::get(const cache_key *key) | |
155 | { | |
156 | if (!map) | |
157 | return NULL; | |
158 | ||
159 | // XXX: replace sfileno with a bigger word (sfileno is only for cache_dirs) | |
160 | sfileno index; | |
161 | const Ipc::StoreMapSlot *const slot = map->openForReading(key, index); | |
162 | if (!slot) | |
163 | return NULL; | |
164 | ||
165 | const Ipc::StoreMapSlot::Basics &basics = slot->basics; | |
166 | const MemStoreMap::Extras &extras = map->extras(index); | |
167 | ||
168 | // create a brand new store entry and initialize it with stored info | |
169 | StoreEntry *e = new StoreEntry(); | |
170 | e->lock_count = 0; | |
171 | ||
172 | e->swap_file_sz = basics.swap_file_sz; | |
173 | e->lastref = basics.lastref; | |
174 | e->timestamp = basics.timestamp; | |
175 | e->expires = basics.expires; | |
176 | e->lastmod = basics.lastmod; | |
177 | e->refcount = basics.refcount; | |
178 | e->flags = basics.flags; | |
179 | ||
180 | e->store_status = STORE_OK; | |
181 | e->mem_status = IN_MEMORY; // setMemStatus(IN_MEMORY) requires mem_obj | |
182 | //e->swap_status = set in StoreEntry constructor to SWAPOUT_NONE; | |
183 | e->ping_status = PING_NONE; | |
184 | ||
185 | EBIT_SET(e->flags, ENTRY_CACHABLE); | |
186 | EBIT_CLR(e->flags, RELEASE_REQUEST); | |
187 | EBIT_CLR(e->flags, KEY_PRIVATE); | |
188 | EBIT_SET(e->flags, ENTRY_VALIDATED); | |
189 | ||
190 | const bool copied = copyFromShm(*e, extras); | |
191 | ||
192 | // we copied everything we could to local memory; no more need to lock | |
193 | map->closeForReading(index); | |
194 | ||
195 | if (copied) { | |
196 | e->hashInsert(key); | |
197 | return e; | |
198 | } | |
199 | ||
200 | debugs(20, 3, HERE << "mem-loading failed; freeing " << index); | |
201 | map->free(index); // do not let others into the same trap | |
202 | return NULL; | |
203 | } | |
204 | ||
205 | void | |
206 | MemStore::get(String const key, STOREGETCLIENT aCallback, void *aCallbackData) | |
207 | { | |
208 | // XXX: not needed but Store parent forces us to implement this | |
209 | fatal("MemStore::get(key,callback,data) should not be called"); | |
210 | } | |
211 | ||
212 | bool | |
213 | MemStore::copyFromShm(StoreEntry &e, const MemStoreMap::Extras &extras) | |
214 | { | |
215 | const Ipc::Mem::PageId &page = extras.page; | |
216 | ||
217 | StoreIOBuffer sourceBuf(extras.storedSize, 0, | |
218 | static_cast<char*>(PagePointer(page))); | |
219 | ||
220 | // XXX: We do not know the URLs yet, only the key, but we need to parse and | |
221 | // store the response for the Root().get() callers to be happy because they | |
222 | // expect IN_MEMORY entries to already have the response headers and body. | |
223 | // At least one caller calls createMemObject() if there is not one, so | |
224 | // we hide the true object until that happens (to avoid leaking TBD URLs). | |
225 | e.createMemObject("TBD", "TBD"); | |
226 | ||
227 | // emulate the usual Store code but w/o inapplicable checks and callbacks: | |
228 | ||
229 | // from store_client::readBody(): | |
230 | HttpReply *rep = (HttpReply *)e.getReply(); | |
231 | const ssize_t end = headersEnd(sourceBuf.data, sourceBuf.length); | |
232 | if (!rep->parseCharBuf(sourceBuf.data, end)) { | |
233 | debugs(20, DBG_IMPORTANT, "Could not parse mem-cached headers: " << e); | |
234 | return false; | |
235 | } | |
236 | // local memory stores both headers and body | |
237 | e.mem_obj->object_sz = sourceBuf.length; // from StoreEntry::complete() | |
238 | ||
239 | storeGetMemSpace(sourceBuf.length); // from StoreEntry::write() | |
240 | ||
241 | assert(e.mem_obj->data_hdr.write(sourceBuf)); // from MemObject::write() | |
242 | const int64_t written = e.mem_obj->endOffset(); | |
30204d23 AR |
243 | // we should write all because StoreEntry::write() never fails |
244 | assert(written >= 0 && | |
245 | static_cast<size_t>(written) == sourceBuf.length); | |
9487bae9 AR |
246 | // would be nice to call validLength() here, but it needs e.key |
247 | ||
248 | debugs(20, 7, HERE << "mem-loaded all " << written << " bytes of " << e << | |
249 | " from " << page); | |
250 | ||
251 | e.hideMemObject(); | |
252 | ||
253 | return true; | |
254 | } | |
255 | ||
256 | void | |
257 | MemStore::considerKeeping(StoreEntry &e) | |
258 | { | |
259 | if (!e.memoryCachable()) { | |
260 | debugs(20, 7, HERE << "Not memory cachable: " << e); | |
261 | return; // cannot keep due to entry state or properties | |
262 | } | |
263 | ||
264 | assert(e.mem_obj); | |
265 | if (!willFit(e.mem_obj->endOffset())) { | |
266 | debugs(20, 5, HERE << "No mem-cache space for " << e); | |
267 | return; // failed to free enough space | |
268 | } | |
269 | ||
270 | keep(e); // may still fail | |
271 | } | |
272 | ||
273 | bool | |
274 | MemStore::willFit(int64_t need) | |
275 | { | |
276 | // TODO: obey configured maximum entry size (with page-based rounding) | |
30204d23 | 277 | return need <= static_cast<int64_t>(Ipc::Mem::PageSize()); |
9487bae9 AR |
278 | } |
279 | ||
280 | /// allocates map slot and calls copyToShm to store the entry in shared memory | |
281 | void | |
282 | MemStore::keep(StoreEntry &e) | |
283 | { | |
284 | if (!map) { | |
285 | debugs(20, 5, HERE << "No map to mem-cache " << e); | |
286 | return; | |
287 | } | |
288 | ||
289 | sfileno index = 0; | |
290 | Ipc::StoreMapSlot *slot = map->openForWriting(reinterpret_cast<const cache_key *>(e.key), index); | |
291 | if (!slot) { | |
292 | debugs(20, 5, HERE << "No room in mem-cache map to index " << e); | |
293 | return; | |
294 | } | |
295 | ||
296 | MemStoreMap::Extras &extras = map->extras(index); | |
297 | if (copyToShm(e, extras)) { | |
298 | slot->set(e); | |
299 | map->closeForWriting(index, false); | |
300 | } else { | |
301 | map->abortIo(index); | |
302 | } | |
303 | } | |
304 | ||
305 | /// uses mem_hdr::copy() to copy local data to shared memory | |
306 | bool | |
307 | MemStore::copyToShm(StoreEntry &e, MemStoreMap::Extras &extras) | |
308 | { | |
309 | Ipc::Mem::PageId page; | |
310 | if (!Ipc::Mem::GetPage(page)) { | |
311 | debugs(20, 5, HERE << "No mem-cache page for " << e); | |
312 | return false; // GetPage is responsible for any cleanup on failures | |
313 | } | |
314 | ||
315 | const int64_t bufSize = Ipc::Mem::PageSize(); | |
316 | const int64_t eSize = e.mem_obj->endOffset(); | |
317 | ||
318 | StoreIOBuffer sharedSpace(bufSize, 0, | |
319 | static_cast<char*>(PagePointer(page))); | |
320 | ||
321 | // check that we kept everything or purge incomplete/sparse cached entry | |
322 | const ssize_t copied = e.mem_obj->data_hdr.copy(sharedSpace); | |
323 | if (eSize != copied) { | |
324 | debugs(20, 2, HERE << "Failed to mem-cache " << e << ": " << | |
325 | eSize << "!=" << copied); | |
326 | // cleanup | |
327 | PutPage(page); | |
328 | return false; | |
329 | } | |
330 | ||
331 | debugs(20, 7, HERE << "mem-cached all " << eSize << " bytes of " << e << | |
332 | " in " << page); | |
333 | ||
17cf0a47 | 334 | theCurrentSize += Ipc::Mem::PageSize(); |
9487bae9 AR |
335 | // remember storage location and size |
336 | extras.page = page; | |
337 | extras.storedSize = copied; | |
338 | return true; | |
339 | } | |
7f6748c8 AR |
340 | |
341 | void | |
342 | MemStore::cleanReadable(const sfileno fileno) | |
343 | { | |
344 | Ipc::Mem::PutPage(map->extras(fileno).page); | |
17cf0a47 | 345 | theCurrentSize -= Ipc::Mem::PageSize(); |
7f6748c8 AR |
346 | } |
347 | ||
a4555399 AR |
348 | /// calculates maximum number of entries we need to store and map |
349 | int64_t | |
350 | MemStore::EntryLimit() | |
351 | { | |
352 | if (!Config.memMaxSize) | |
353 | return 0; // no memory cache configured | |
354 | ||
a4555399 AR |
355 | const int64_t entrySize = Ipc::Mem::PageSize(); // for now |
356 | const int64_t entryLimit = Config.memMaxSize / entrySize; | |
a4555399 AR |
357 | return entryLimit; |
358 | } | |
359 | ||
360 | ||
361 | /// initializes shared memory segments used by MemStore | |
362 | class MemStoreRr: public RegisteredRunner | |
363 | { | |
364 | public: | |
365 | /* RegisteredRunner API */ | |
366 | virtual void run(const RunnerRegistry &); | |
c011f9bc | 367 | virtual ~MemStoreRr(); |
a4555399 AR |
368 | }; |
369 | ||
370 | RunnerRegistrationEntry(rrAfterConfig, MemStoreRr); | |
371 | ||
372 | ||
373 | void MemStoreRr::run(const RunnerRegistry &) | |
374 | { | |
375 | // XXX: restore if (!UsingSmp()) return; | |
376 | ||
377 | if (IamMasterProcess()) | |
378 | MemStore::Init(); | |
379 | } | |
c011f9bc DK |
380 | |
381 | MemStoreRr::~MemStoreRr() | |
382 | { | |
383 | // XXX: restore if (!UsingSmp()) return; | |
384 | ||
385 | if (IamMasterProcess()) | |
386 | MemStoreMap::Unlink(ShmLabel); | |
387 | } |