]>
Commit | Line | Data |
---|---|---|
9cef6668 | 1 | /* |
f70aedc4 | 2 | * Copyright (C) 1996-2021 The Squid Software Foundation and contributors |
9cef6668 | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
9cef6668 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 20 Storage Manager Swapout Functions */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
aa839030 | 12 | #include "cbdata.h" |
4310f8b0 | 13 | #include "CollapsedForwarding.h" |
af69c635 | 14 | #include "globals.h" |
e6ccf245 | 15 | #include "Store.h" |
602d9612 | 16 | #include "StoreClient.h" |
9837567d | 17 | // TODO: Abstract the use of this more |
528b2c61 | 18 | #include "mem_node.h" |
19 | #include "MemObject.h" | |
4d5904f7 | 20 | #include "SquidConfig.h" |
e4f1fdae | 21 | #include "StatCounters.h" |
2745fea5 AR |
22 | #include "store/Disk.h" |
23 | #include "store/Disks.h" | |
b3f7fd88 AR |
24 | #include "store_log.h" |
25 | #include "swap_log_op.h" | |
f09f5b26 | 26 | |
d54f0ab3 | 27 | static void storeSwapOutStart(StoreEntry * e); |
4fcc8876 | 28 | static StoreIOState::STIOCB storeSwapOutFileClosed; |
29 | static StoreIOState::STFNCB storeSwapOutFileNotify; | |
f09f5b26 | 30 | |
daacd51f AJ |
31 | // wrapper to cross C/C++ ABI boundary. xfree is extern "C" for libraries. |
32 | static void xfree_cppwrapper(void *x) | |
33 | { | |
34 | xfree(x); | |
35 | } | |
36 | ||
f09f5b26 | 37 | /* start swapping object to disk */ |
d54f0ab3 | 38 | static void |
f09f5b26 | 39 | storeSwapOutStart(StoreEntry * e) |
40 | { | |
2391a162 | 41 | MemObject *mem = e->mem_obj; |
d3b3ab85 | 42 | StoreIOState::Pointer sio; |
2391a162 | 43 | assert(mem); |
cd748f27 | 44 | /* Build the swap metadata, so the filesystem will know how much |
45 | * metadata there is to store | |
46 | */ | |
26ac0430 AJ |
47 | debugs(20, 5, "storeSwapOutStart: Begin SwapOut '" << e->url() << "' to dirno " << |
48 | e->swap_dirn << ", fileno " << std::hex << std::setw(8) << std::setfill('0') << | |
bf8fe701 | 49 | std::uppercase << e->swap_filen); |
0cdcf3d7 | 50 | e->swapOutDecision(MemObject::SwapOut::swStarted); |
528b2c61 | 51 | /* If we start swapping out objects with OutOfBand Metadata, |
52 | * then this code needs changing | |
53 | */ | |
246e6cc1 AJ |
54 | |
55 | /* TODO: make some sort of data,size refcounted immutable buffer | |
56 | * and stop fooling ourselves with "const char*" buffers. | |
57 | */ | |
58 | ||
59 | // Create metadata now, possibly in vain: storeCreate needs swap_hdr_sz. | |
66d51f4f | 60 | const auto buf = e->getSerialisedMetaData(mem->swap_hdr_sz); |
246e6cc1 AJ |
61 | assert(buf); |
62 | ||
cd748f27 | 63 | /* Create the swap file */ |
aa839030 | 64 | generic_cbdata *c = new generic_cbdata(e); |
fa80a8ef | 65 | sio = storeCreate(e, storeSwapOutFileNotify, storeSwapOutFileClosed, c); |
62e76326 | 66 | |
85a4b153 | 67 | if (sio == NULL) { |
4310f8b0 | 68 | assert(!e->hasDisk()); |
62e76326 | 69 | e->swap_status = SWAPOUT_NONE; |
0cdcf3d7 | 70 | e->swapOutDecision(MemObject::SwapOut::swImpossible); |
aa839030 | 71 | delete c; |
246e6cc1 | 72 | xfree((char*)buf); |
62e76326 | 73 | storeLog(STORE_LOG_SWAPOUTFAIL, e); |
74 | return; | |
3fc29499 | 75 | } |
62e76326 | 76 | |
528b2c61 | 77 | mem->swapout.sio = sio; |
78 | /* Don't lock until after create, or the replacement | |
79 | * code might get confused */ | |
34266cde | 80 | |
1bfe9ade | 81 | e->lock("storeSwapOutStart"); |
cd748f27 | 82 | /* Pick up the file number if it was assigned immediately */ |
4310f8b0 | 83 | e->attachToDisk(mem->swapout.sio->swap_dirn, mem->swapout.sio->swap_filen, SWAPOUT_WRITING); |
34266cde | 84 | |
cd748f27 | 85 | /* write out the swap metadata */ |
daacd51f | 86 | storeIOWrite(mem->swapout.sio, buf, mem->swap_hdr_sz, 0, xfree_cppwrapper); |
f09f5b26 | 87 | } |
88 | ||
4310f8b0 | 89 | /// XXX: unused, see a related StoreIOState::file_callback |
cd748f27 | 90 | static void |
02ba667b | 91 | storeSwapOutFileNotify(void *, int, StoreIOState::Pointer) |
cd748f27 | 92 | { |
02ba667b | 93 | assert(false); |
cd748f27 | 94 | } |
95 | ||
90e8b325 | 96 | static bool |
528b2c61 | 97 | doPages(StoreEntry *anEntry) |
98 | { | |
99 | MemObject *mem = anEntry->mem_obj; | |
62e76326 | 100 | |
528b2c61 | 101 | do { |
aa1a691e AR |
102 | // find the page containing the first byte we have not swapped out yet |
103 | mem_node *page = | |
104 | mem->data_hdr.getBlockContainingLocation(mem->swapout.queue_offset); | |
62e76326 | 105 | |
aa1a691e | 106 | if (!page) |
90e8b325 | 107 | break; // wait for more data to become available |
aa1a691e AR |
108 | |
109 | // memNodeWriteComplete() and absence of buffer offset math below | |
110 | // imply that we always write from the very beginning of the page | |
111 | assert(page->start() == mem->swapout.queue_offset); | |
62e76326 | 112 | |
113 | /* | |
114 | * Get the length of this buffer. We are assuming(!) that the buffer | |
115 | * length won't change on this buffer, or things are going to be very | |
116 | * strange. I think that after the copy to a buffer is done, the buffer | |
117 | * size should stay fixed regardless so that this code isn't confused, | |
118 | * but we can look at this at a later date or whenever the code results | |
119 | * in bad swapouts, whichever happens first. :-) | |
120 | */ | |
aa1a691e | 121 | ssize_t swap_buf_len = page->nodeBuffer.length; |
62e76326 | 122 | |
4a7a3d56 | 123 | debugs(20, 3, "storeSwapOut: swap_buf_len = " << swap_buf_len); |
62e76326 | 124 | |
125 | assert(swap_buf_len > 0); | |
126 | ||
4a7a3d56 | 127 | debugs(20, 3, "storeSwapOut: swapping out " << swap_buf_len << " bytes from " << mem->swapout.queue_offset); |
62e76326 | 128 | |
129 | mem->swapout.queue_offset += swap_buf_len; | |
130 | ||
90e8b325 AR |
131 | // Quit if write() fails. Sio is going to call our callback, and that |
132 | // will cleanup, but, depending on the fs, that call may be async. | |
133 | const bool ok = mem->swapout.sio->write( | |
9d4e9cfb AR |
134 | mem->data_hdr.NodeGet(page), |
135 | swap_buf_len, | |
136 | -1, | |
137 | memNodeWriteComplete); | |
62e76326 | 138 | |
4310f8b0 | 139 | if (!ok || !anEntry->swappingOut()) |
90e8b325 | 140 | return false; |
62e76326 | 141 | |
47f6e231 | 142 | int64_t swapout_size = mem->endOffset() - mem->swapout.queue_offset; |
62e76326 | 143 | |
144 | if (anEntry->store_status == STORE_PENDING) | |
145 | if (swapout_size < SM_PAGE_SIZE) | |
146 | break; | |
147 | ||
148 | if (swapout_size <= 0) | |
90e8b325 | 149 | break; |
528b2c61 | 150 | } while (true); |
90e8b325 AR |
151 | |
152 | // either wait for more data or call swapOutFileClose() | |
153 | return true; | |
528b2c61 | 154 | } |
155 | ||
528b2c61 | 156 | /* This routine is called every time data is sent to the client side. |
157 | * It's overhead is therefor, significant. | |
158 | */ | |
f09f5b26 | 159 | void |
c07cbbf4 | 160 | StoreEntry::swapOut() |
f09f5b26 | 161 | { |
c07cbbf4 | 162 | if (!mem_obj) |
62e76326 | 163 | return; |
164 | ||
5b55f1f1 CT |
165 | // this flag may change so we must check even if we are swappingOut |
166 | if (EBIT_TEST(flags, ENTRY_ABORTED)) { | |
167 | assert(EBIT_TEST(flags, RELEASE_REQUEST)); | |
168 | // StoreEntry::abort() already closed the swap out file, if any | |
169 | // no trimming: data producer must stop production if ENTRY_ABORTED | |
62e76326 | 170 | return; |
5b55f1f1 CT |
171 | } |
172 | ||
173 | const bool weAreOrMayBeSwappingOut = swappingOut() || mayStartSwapOut(); | |
55bfc9fc | 174 | |
4475555f | 175 | Store::Root().memoryOut(*this, weAreOrMayBeSwappingOut); |
5b55f1f1 | 176 | |
2d4dea47 | 177 | if (mem_obj->swapout.decision < MemObject::SwapOut::swPossible) |
5b55f1f1 | 178 | return; // nothing else to do |
528b2c61 | 179 | |
aa1a691e AR |
180 | // Aborted entries have STORE_OK, but swapoutPossible rejects them. Thus, |
181 | // store_status == STORE_OK below means we got everything we wanted. | |
182 | ||
47f6e231 | 183 | debugs(20, 7, HERE << "storeSwapOut: mem->inmem_lo = " << mem_obj->inmem_lo); |
184 | debugs(20, 7, HERE << "storeSwapOut: mem->endOffset() = " << mem_obj->endOffset()); | |
185 | debugs(20, 7, HERE << "storeSwapOut: swapout.queue_offset = " << mem_obj->swapout.queue_offset); | |
62e76326 | 186 | |
85a4b153 | 187 | if (mem_obj->swapout.sio != NULL) |
26ac0430 | 188 | debugs(20, 7, "storeSwapOut: storeOffset() = " << mem_obj->swapout.sio->offset() ); |
62e76326 | 189 | |
47f6e231 | 190 | int64_t const lowest_offset = mem_obj->lowestMemReaderOffset(); |
62e76326 | 191 | |
47f6e231 | 192 | debugs(20, 7, HERE << "storeSwapOut: lowest_offset = " << lowest_offset); |
62e76326 | 193 | |
5aecb102 | 194 | #if SIZEOF_OFF_T <= 4 |
62e76326 | 195 | |
c07cbbf4 | 196 | if (mem_obj->endOffset() > 0x7FFF0000) { |
fa84c01d | 197 | debugs(20, DBG_CRITICAL, "WARNING: preventing off_t overflow for " << url()); |
bfb55b6f | 198 | abort(); |
62e76326 | 199 | return; |
adcceb47 | 200 | } |
62e76326 | 201 | |
adcceb47 | 202 | #endif |
4310f8b0 | 203 | if (swappingOut()) |
47f6e231 | 204 | assert(mem_obj->inmem_lo <= mem_obj->objectBytesOnDisk() ); |
62e76326 | 205 | |
5b55f1f1 CT |
206 | // buffered bytes we have not swapped out yet |
207 | const int64_t swapout_maxsize = mem_obj->availableForSwapOut(); | |
208 | assert(swapout_maxsize >= 0); | |
4a7a3d56 | 209 | debugs(20, 7, "storeSwapOut: swapout_size = " << swapout_maxsize); |
62e76326 | 210 | |
aa1a691e AR |
211 | if (swapout_maxsize == 0) { // swapped everything we got |
212 | if (store_status == STORE_OK) { // got everything we wanted | |
213 | assert(mem_obj->object_sz >= 0); | |
214 | swapOutFileClose(StoreIOState::wroteAll); | |
215 | } | |
216 | // else need more data to swap out | |
217 | return; | |
1b72bda1 | 218 | } |
62e76326 | 219 | |
c07cbbf4 | 220 | if (store_status == STORE_PENDING) { |
62e76326 | 221 | /* wait for a full block to write */ |
222 | ||
223 | if (swapout_maxsize < SM_PAGE_SIZE) | |
224 | return; | |
225 | ||
226 | /* | |
227 | * Wait until we are below the disk FD limit, only if the | |
d5430dc8 | 228 | * next read won't be deferred. |
62e76326 | 229 | */ |
c07cbbf4 | 230 | if (storeTooManyDiskFilesOpen() && !checkDeferRead(-1)) |
62e76326 | 231 | return; |
c47511fd | 232 | } |
62e76326 | 233 | |
2391a162 | 234 | /* Ok, we have stuff to swap out. Is there a swapout.sio open? */ |
4310f8b0 | 235 | if (!hasDisk()) { |
c07cbbf4 | 236 | assert(mem_obj->swapout.sio == NULL); |
237 | assert(mem_obj->inmem_lo == 0); | |
ddc9b32c | 238 | storeSwapOutStart(this); // sets SwapOut::swImpossible on failures |
f09f5b26 | 239 | } |
62e76326 | 240 | |
c07cbbf4 | 241 | if (mem_obj->swapout.sio == NULL) |
62e76326 | 242 | return; |
243 | ||
90e8b325 | 244 | if (!doPages(this)) |
62e76326 | 245 | /* oops, we're not swapping out any more */ |
246 | return; | |
247 | ||
c07cbbf4 | 248 | if (store_status == STORE_OK) { |
62e76326 | 249 | /* |
250 | * If the state is STORE_OK, then all data must have been given | |
251 | * to the filesystem at this point because storeSwapOut() is | |
252 | * not going to be called again for this entry. | |
253 | */ | |
aa1a691e | 254 | assert(mem_obj->object_sz >= 0); |
c07cbbf4 | 255 | assert(mem_obj->endOffset() == mem_obj->swapout.queue_offset); |
aa1a691e | 256 | swapOutFileClose(StoreIOState::wroteAll); |
1792fbdb | 257 | } |
f09f5b26 | 258 | } |
259 | ||
260 | void | |
aa1a691e | 261 | StoreEntry::swapOutFileClose(int how) |
f09f5b26 | 262 | { |
c07cbbf4 | 263 | assert(mem_obj != NULL); |
aa1a691e | 264 | debugs(20, 3, "storeSwapOutFileClose: " << getMD5Text() << " how=" << how); |
bf8fe701 | 265 | debugs(20, 3, "storeSwapOutFileClose: sio = " << mem_obj->swapout.sio.getRaw()); |
62e76326 | 266 | |
c07cbbf4 | 267 | if (mem_obj->swapout.sio == NULL) |
62e76326 | 268 | return; |
269 | ||
aa1a691e | 270 | storeClose(mem_obj->swapout.sio, how); |
f09f5b26 | 271 | } |
272 | ||
273 | static void | |
e5de8b13 | 274 | storeSwapOutFileClosed(void *data, int errflag, StoreIOState::Pointer self) |
f09f5b26 | 275 | { |
bd6e2f16 AJ |
276 | StoreEntry *e; |
277 | static_cast<generic_cbdata *>(data)->unwrap(&e); | |
278 | ||
25354045 | 279 | MemObject *mem = e->mem_obj; |
84177444 | 280 | assert(mem->swapout.sio == self); |
4310f8b0 | 281 | assert(e->swappingOut()); |
62e76326 | 282 | |
aa1a691e AR |
283 | // if object_size is still unknown, the entry was probably aborted |
284 | if (errflag || e->objectLen() < 0) { | |
de4bc0ea | 285 | debugs(20, 2, "storeSwapOutFileClosed: dirno " << e->swap_dirn << ", swapfile " << |
26ac0430 | 286 | std::hex << std::setw(8) << std::setfill('0') << std::uppercase << |
bf8fe701 | 287 | e->swap_filen << ", errflag=" << errflag); |
62e76326 | 288 | |
289 | if (errflag == DISK_NO_SPACE_LEFT) { | |
9837567d | 290 | /* TODO: this should be handle by the link from store IO to |
c8f4eac4 | 291 | * Store, rather than being a top level API call. |
292 | */ | |
2745fea5 | 293 | e->disk().diskFull(); |
62e76326 | 294 | storeConfigure(); |
295 | } | |
296 | ||
02ba667b EB |
297 | // mark the locked entry for deletion |
298 | // TODO: Keep the memory entry (if any) | |
299 | e->releaseRequest(); | |
300 | e->swap_status = SWAPOUT_FAILED; | |
4310f8b0 | 301 | e->disk().finalizeSwapoutFailure(*e); |
2391a162 | 302 | } else { |
62e76326 | 303 | /* swapping complete */ |
26ac0430 AJ |
304 | debugs(20, 3, "storeSwapOutFileClosed: SwapOut complete: '" << e->url() << "' to " << |
305 | e->swap_dirn << ", " << std::hex << std::setw(8) << std::setfill('0') << | |
bf8fe701 | 306 | std::uppercase << e->swap_filen); |
aa1a691e AR |
307 | debugs(20, 5, HERE << "swap_file_sz = " << |
308 | e->objectLen() << " + " << mem->swap_hdr_sz); | |
8061c9b2 | 309 | |
707fdc47 | 310 | e->swap_file_sz = e->objectLen() + mem->swap_hdr_sz; |
62e76326 | 311 | e->swap_status = SWAPOUT_DONE; |
4310f8b0 | 312 | e->disk().finalizeSwapoutSuccess(*e); |
62e76326 | 313 | |
ddc9b32c AR |
314 | // XXX: For some Stores, it is pointless to re-check cachability here |
315 | // and it leads to double counts in store_check_cachable_hist. We need | |
316 | // another way to signal a completed but failed swapout. Or, better, | |
317 | // each Store should handle its own logging and LOG state setting. | |
3900307b | 318 | if (e->checkCachable()) { |
62e76326 | 319 | storeLog(STORE_LOG_SWAPOUT, e); |
320 | storeDirSwapLog(e, SWAP_LOG_ADD); | |
321 | } | |
322 | ||
e4f1fdae | 323 | ++statCounter.swap.outs; |
f09f5b26 | 324 | } |
62e76326 | 325 | |
4310f8b0 | 326 | Store::Root().transientsCompleteWriting(*e); |
bf8fe701 | 327 | debugs(20, 3, "storeSwapOutFileClosed: " << __FILE__ << ":" << __LINE__); |
d3b3ab85 | 328 | mem->swapout.sio = NULL; |
1bfe9ade | 329 | e->unlock("storeSwapOutFileClosed"); |
f09f5b26 | 330 | } |
61038223 | 331 | |
c07cbbf4 | 332 | bool |
5b55f1f1 | 333 | StoreEntry::mayStartSwapOut() |
c2725718 | 334 | { |
5b55f1f1 CT |
335 | // must be checked in the caller |
336 | assert(!EBIT_TEST(flags, ENTRY_ABORTED)); | |
4094b311 | 337 | assert(!swappingOut()); |
5b55f1f1 CT |
338 | |
339 | if (!Config.cacheSwap.n_configured) | |
340 | return false; | |
341 | ||
342 | assert(mem_obj); | |
0cdcf3d7 | 343 | const MemObject::SwapOut::Decision &decision = mem_obj->swapout.decision; |
5b55f1f1 | 344 | |
0e3b2ff0 | 345 | // if we decided that starting is not possible, do not repeat same checks |
5b55f1f1 CT |
346 | if (decision == MemObject::SwapOut::swImpossible) { |
347 | debugs(20, 3, HERE << " already rejected"); | |
348 | return false; | |
349 | } | |
350 | ||
4310f8b0 EB |
351 | // if we are swapping out or swapped out already, do not start over |
352 | if (hasDisk() || Store::Root().hasReadableDiskEntry(*this)) { | |
539283df | 353 | debugs(20, 3, "already did"); |
0cdcf3d7 | 354 | swapOutDecision(MemObject::SwapOut::swImpossible); |
4094b311 | 355 | return false; |
5b55f1f1 CT |
356 | } |
357 | ||
4310f8b0 EB |
358 | // if we have just stared swapping out (attachToDisk() has not been |
359 | // called), do not start over | |
0e3b2ff0 AR |
360 | if (decision == MemObject::SwapOut::swStarted) { |
361 | debugs(20, 3, "already started"); | |
0cdcf3d7 | 362 | swapOutDecision(MemObject::SwapOut::swImpossible); |
0e3b2ff0 AR |
363 | return false; |
364 | } | |
365 | ||
4310f8b0 EB |
366 | if (Store::Root().markedForDeletionAndAbandoned(*this)) { |
367 | debugs(20, 3, "marked for deletion and abandoned"); | |
368 | swapOutDecision(MemObject::SwapOut::swImpossible); | |
369 | return false; | |
370 | } | |
371 | ||
0e3b2ff0 AR |
372 | // if we decided that swapout is possible, do not repeat same checks |
373 | if (decision == MemObject::SwapOut::swPossible) { | |
374 | debugs(20, 3, "already allowed"); | |
375 | return true; | |
376 | } | |
377 | ||
5b55f1f1 CT |
378 | if (!checkCachable()) { |
379 | debugs(20, 3, HERE << "not cachable"); | |
0cdcf3d7 | 380 | swapOutDecision(MemObject::SwapOut::swImpossible); |
5b55f1f1 CT |
381 | return false; |
382 | } | |
383 | ||
384 | if (EBIT_TEST(flags, ENTRY_SPECIAL)) { | |
385 | debugs(20, 3, HERE << url() << " SPECIAL"); | |
0cdcf3d7 | 386 | swapOutDecision(MemObject::SwapOut::swImpossible); |
5b55f1f1 CT |
387 | return false; |
388 | } | |
389 | ||
e0397786 AR |
390 | if (mem_obj->inmem_lo > 0) { |
391 | debugs(20, 3, "storeSwapOut: (inmem_lo > 0) imem_lo:" << mem_obj->inmem_lo); | |
0cdcf3d7 | 392 | swapOutDecision(MemObject::SwapOut::swImpossible); |
e0397786 AR |
393 | return false; |
394 | } | |
395 | ||
396 | if (!mem_obj->isContiguous()) { | |
397 | debugs(20, 3, "storeSwapOut: not Contiguous"); | |
0cdcf3d7 | 398 | swapOutDecision(MemObject::SwapOut::swImpossible); |
e0397786 AR |
399 | return false; |
400 | } | |
401 | ||
3c856e95 AR |
402 | // handle store_maxobjsize limit |
403 | { | |
5b55f1f1 CT |
404 | // TODO: add estimated store metadata size to be conservative |
405 | ||
406 | // use guaranteed maximum if it is known | |
407 | const int64_t expectedEnd = mem_obj->expectedReplySize(); | |
408 | debugs(20, 7, HERE << "expectedEnd = " << expectedEnd); | |
409 | if (expectedEnd > store_maxobjsize) { | |
410 | debugs(20, 3, HERE << "will not fit: " << expectedEnd << | |
411 | " > " << store_maxobjsize); | |
0cdcf3d7 | 412 | swapOutDecision(MemObject::SwapOut::swImpossible); |
5b55f1f1 CT |
413 | return false; // known to outgrow the limit eventually |
414 | } | |
415 | ||
416 | // use current minimum (always known) | |
417 | const int64_t currentEnd = mem_obj->endOffset(); | |
418 | if (currentEnd > store_maxobjsize) { | |
419 | debugs(20, 3, HERE << "does not fit: " << currentEnd << | |
420 | " > " << store_maxobjsize); | |
0cdcf3d7 | 421 | swapOutDecision(MemObject::SwapOut::swImpossible); |
5b55f1f1 CT |
422 | return false; // already does not fit and may only get bigger |
423 | } | |
424 | ||
e0397786 AR |
425 | // prevent final default swPossible answer for yet unknown length |
426 | if (expectedEnd < 0 && store_status != STORE_OK) { | |
5ca027f0 AR |
427 | const int64_t more = Store::Root().accumulateMore(*this); |
428 | if (more > 0) { | |
429 | debugs(20, 5, "got " << currentEnd << "; defer decision for " << more << " more bytes"); | |
430 | return true; // may still fit, but no final decision yet | |
431 | } | |
5b55f1f1 | 432 | } |
06d2839d | 433 | } |
62e76326 | 434 | |
0cdcf3d7 | 435 | swapOutDecision(MemObject::SwapOut::swPossible); |
c07cbbf4 | 436 | return true; |
c2725718 | 437 | } |
f53969cc | 438 |