Merge branch 'fleischer-dc17'
[polipo.git] / object.c
blob7c3b92203bb411227c4e8a411c8964c83ec5540c
1 /*
2 Copyright (c) 2003-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
23 #include "polipo.h"
25 int mindlesslyCacheVary = 0;
26 int objectHashTableSize = 0;
27 int log2ObjectHashTableSize;
29 static ObjectPtr object_list = NULL;
30 static ObjectPtr object_list_end = NULL;
32 int objectExpiryScheduled;
34 int publicObjectCount;
35 int privateObjectCount;
36 int cacheIsShared = 1;
37 int publicObjectLowMark = 0, objectHighMark = 2048;
39 static ObjectPtr *objectHashTable;
40 int maxExpiresAge = (30 * 24 + 1) * 3600;
41 int maxAge = (14 * 24 + 1) * 3600;
42 float maxAgeFraction = 0.1;
43 int maxNoModifiedAge = 23 * 60;
44 int maxWriteoutWhenIdle = 64 * 1024;
45 int maxObjectsWhenIdle = 32;
46 int idleTime = 20;
47 int dontCacheCookies = 0;
49 void
50 preinitObject()
52 CONFIG_VARIABLE_SETTABLE(idleTime, CONFIG_TIME, configIntSetter,
53 "Time to remain idle before writing out.");
54 CONFIG_VARIABLE_SETTABLE(maxWriteoutWhenIdle, CONFIG_INT, configIntSetter,
55 "Amount of data to write at a time when idle.");
56 CONFIG_VARIABLE_SETTABLE(maxObjectsWhenIdle, CONFIG_INT, configIntSetter,
57 "Number of objects to write at a time "
58 "when idle.");
59 CONFIG_VARIABLE_SETTABLE(cacheIsShared, CONFIG_BOOLEAN, configIntSetter,
60 "If false, ignore s-maxage and private.");
61 CONFIG_VARIABLE_SETTABLE(mindlesslyCacheVary, CONFIG_BOOLEAN,
62 configIntSetter,
63 "If true, mindlessly cache negotiated objects.");
64 CONFIG_VARIABLE(objectHashTableSize, CONFIG_INT,
65 "Size of the object hash table (0 = auto).");
66 CONFIG_VARIABLE(objectHighMark, CONFIG_INT,
67 "High object count mark.");
68 CONFIG_VARIABLE(publicObjectLowMark, CONFIG_INT,
69 "Low object count mark (0 = auto).");
70 CONFIG_VARIABLE_SETTABLE(maxExpiresAge, CONFIG_TIME, configIntSetter,
71 "Max age for objects with Expires header.");
72 CONFIG_VARIABLE_SETTABLE(maxAge, CONFIG_TIME, configIntSetter,
73 "Max age for objects without Expires header.");
74 CONFIG_VARIABLE_SETTABLE(maxAgeFraction, CONFIG_FLOAT, configFloatSetter,
75 "Fresh fraction of modification time.");
76 CONFIG_VARIABLE_SETTABLE(maxNoModifiedAge, CONFIG_TIME, configIntSetter,
77 "Max age for objects without Last-modified.");
78 CONFIG_VARIABLE_SETTABLE(dontCacheCookies, CONFIG_BOOLEAN, configIntSetter,
79 "Work around cachable cookies.");
82 void
83 initObject()
85 int q;
86 if(objectHighMark < 16) {
87 objectHighMark = 16;
88 do_log(L_WARN, "Impossibly low objectHighMark -- setting to %d.\n",
89 objectHighMark);
92 q = 0;
93 if(publicObjectLowMark == 0) q = 1;
94 if(publicObjectLowMark < 8 || publicObjectLowMark >= objectHighMark - 4) {
95 publicObjectLowMark = objectHighMark / 2;
96 if(!q)
97 do_log(L_WARN, "Impossible publicObjectLowMark value -- "
98 "setting to %d.\n", publicObjectLowMark);
101 q = 1;
102 if(objectHashTableSize <= objectHighMark / 2 ||
103 objectHashTableSize > objectHighMark * 1024) {
104 if(objectHashTableSize != 0) q = 0;
105 objectHashTableSize = objectHighMark * 16;
107 log2ObjectHashTableSize = log2_ceil(objectHashTableSize);
108 objectHashTableSize = 1 << log2ObjectHashTableSize;
109 if(!q)
110 do_log(L_WARN, "Suspicious objectHashTableSize value -- "
111 "setting to %d.\n", objectHashTableSize);
113 object_list = NULL;
114 object_list_end = NULL;
115 publicObjectCount = 0;
116 privateObjectCount = 0;
117 objectHashTable = calloc(1 << log2ObjectHashTableSize,
118 sizeof(ObjectPtr));
119 if(!objectHashTable) {
120 do_log(L_ERROR, "Couldn't allocate object hash table.\n");
121 exit(1);
125 ObjectPtr
126 findObject(int type, const void *key, int key_size)
128 int h;
129 ObjectPtr object;
131 if(key_size >= 50000)
132 return NULL;
134 h = hash(type, key, key_size, log2ObjectHashTableSize);
135 object = objectHashTable[h];
136 if(!object)
137 return NULL;
138 if(object->type != type || object->key_size != key_size ||
139 memcmp(object->key, key, key_size) != 0) {
140 return NULL;
142 if(object->next)
143 object->next->previous = object->previous;
144 if(object->previous)
145 object->previous->next = object->next;
146 if(object_list == object)
147 object_list = object->next;
148 if(object_list_end == object)
149 object_list_end = object->previous;
150 object->previous = NULL;
151 object->next = object_list;
152 if(object_list)
153 object_list->previous = object;
154 object_list = object;
155 if(!object_list_end)
156 object_list_end = object;
157 return retainObject(object);
160 ObjectPtr
161 makeObject(int type, const void *key, int key_size, int public, int fromdisk,
162 RequestFunction request, void* request_closure)
164 ObjectPtr object;
165 int h;
167 object = findObject(type, key, key_size);
168 if(object != NULL) {
169 if(public)
170 return object;
171 else
172 privatiseObject(object, 0);
175 if(publicObjectCount + privateObjectCount >= objectHighMark) {
176 if(!objectExpiryScheduled)
177 discardObjects(0, 0);
178 if(publicObjectCount + privateObjectCount >= objectHighMark) {
179 return NULL;
183 if(publicObjectCount >= publicObjectLowMark &&
184 !objectExpiryScheduled) {
185 TimeEventHandlerPtr event;
186 event = scheduleTimeEvent(-1, discardObjectsHandler, 0, NULL);
187 if(event)
188 objectExpiryScheduled = 1;
189 else
190 do_log(L_ERROR, "Couldn't schedule object expiry.\n");
193 object = malloc(sizeof(ObjectRec));
194 if(object == NULL)
195 return NULL;
197 object->type = type;
198 object->request = request;
199 object->request_closure = request_closure;
200 object->key = malloc(key_size + 1);
201 if(object->key == NULL) {
202 free(object);
203 return NULL;
205 memcpy(object->key, key, key_size);
206 /* In order to make it more convenient to use keys as strings,
207 they are NUL-terminated. */
208 object->key[key_size] = '\0';
209 object->key_size = key_size;
210 object->flags = (public?OBJECT_PUBLIC:0) | OBJECT_INITIAL;
211 if(public) {
212 h = hash(object->type, object->key, object->key_size,
213 log2ObjectHashTableSize);
214 if(objectHashTable[h]) {
215 writeoutToDisk(objectHashTable[h], objectHashTable[h]->size, -1);
216 privatiseObject(objectHashTable[h], 0);
217 assert(!objectHashTable[h]);
219 objectHashTable[h] = object;
220 object->next = object_list;
221 object->previous = NULL;
222 if(object_list)
223 object_list->previous = object;
224 object_list = object;
225 if(!object_list_end)
226 object_list_end = object;
227 } else {
228 object->next = NULL;
229 object->previous = NULL;
231 object->abort_data = NULL;
232 object->code = 0;
233 object->message = NULL;
234 initCondition(&object->condition);
235 object->headers = NULL;
236 object->via = NULL;
237 object->numchunks = 0;
238 object->chunks = NULL;
239 object->length = -1;
240 object->date = -1;
241 object->age = -1;
242 object->expires = -1;
243 object->last_modified = -1;
244 object->atime = -1;
245 object->etag = NULL;
246 object->cache_control = 0;
247 object->max_age = -1;
248 object->s_maxage = -1;
249 object->size = 0;
250 object->requestor = NULL;
251 object->disk_entry = NULL;
252 if(object->flags & OBJECT_PUBLIC)
253 publicObjectCount++;
254 else
255 privateObjectCount++;
256 object->refcount = 1;
258 if(public && fromdisk)
259 objectGetFromDisk(object);
260 return object;
263 void
264 objectMetadataChanged(ObjectPtr object, int revalidate)
266 if(revalidate) {
267 revalidateDiskEntry(object);
268 } else {
269 object->flags &= ~OBJECT_DISK_ENTRY_COMPLETE;
270 dirtyDiskEntry(object);
272 return;
275 ObjectPtr
276 retainObject(ObjectPtr object)
278 do_log(D_REFCOUNT, "O 0x%lx %d++\n",
279 (unsigned long)object, object->refcount);
280 object->refcount++;
281 return object;
284 void
285 releaseObject(ObjectPtr object)
287 do_log(D_REFCOUNT, "O 0x%lx %d--\n",
288 (unsigned long)object, object->refcount);
289 object->refcount--;
290 if(object->refcount == 0) {
291 assert(!object->condition.handlers &&
292 !(object->flags & OBJECT_INPROGRESS));
293 if(!(object->flags & OBJECT_PUBLIC))
294 destroyObject(object);
298 void
299 releaseNotifyObject(ObjectPtr object)
301 do_log(D_REFCOUNT, "O 0x%lx %d--\n",
302 (unsigned long)object, object->refcount);
303 object->refcount--;
304 if(object->refcount > 0) {
305 notifyObject(object);
306 } else {
307 assert(!object->condition.handlers &&
308 !(object->flags & OBJECT_INPROGRESS));
309 if(!(object->flags & OBJECT_PUBLIC))
310 destroyObject(object);
314 void
315 lockChunk(ObjectPtr object, int i)
317 do_log(D_LOCK, "Lock 0x%lx[%d]: ", (unsigned long)object, i);
318 assert(i >= 0);
319 if(i >= object->numchunks)
320 objectSetChunks(object, i + 1);
321 object->chunks[i].locked++;
322 do_log(D_LOCK, "%d\n", object->chunks[i].locked);
325 void
326 unlockChunk(ObjectPtr object, int i)
328 do_log(D_LOCK, "Unlock 0x%lx[%d]: ", (unsigned long)object, i);
329 assert(i >= 0 && i < object->numchunks);
330 assert(object->chunks[i].locked > 0);
331 object->chunks[i].locked--;
332 do_log(D_LOCK, "%d\n", object->chunks[i].locked);
336 objectSetChunks(ObjectPtr object, int numchunks)
338 int n;
340 if(numchunks <= object->numchunks)
341 return 0;
343 if(object->length >= 0)
344 n = MAX(numchunks, (object->length + (CHUNK_SIZE - 1)) / CHUNK_SIZE);
345 else
346 n = MAX(numchunks,
347 MAX(object->numchunks + 2, object->numchunks * 5 / 4));
349 if(n == 0) {
350 assert(object->chunks == NULL);
351 } else if(object->numchunks == 0) {
352 object->chunks = calloc(n, sizeof(ChunkRec));
353 if(object->chunks == NULL) {
354 return -1;
356 object->numchunks = n;
357 } else {
358 ChunkPtr newchunks;
359 newchunks = realloc(object->chunks, n * sizeof(ChunkRec));
360 if(newchunks == NULL)
361 return -1;
362 memset(newchunks + object->numchunks, 0,
363 (n - object->numchunks) * sizeof(ChunkRec));
364 object->chunks = newchunks;
365 object->numchunks = n;
367 return 0;
370 ObjectPtr
371 objectPartial(ObjectPtr object, int length, struct _Atom *headers)
373 object->headers = headers;
375 if(length >= 0) {
376 if(object->size > length) {
377 abortObject(object, 502,
378 internAtom("Inconsistent Content-Length"));
379 notifyObject(object);
380 return object;
384 if(length >= 0)
385 object->length = length;
387 object->flags &= ~OBJECT_INITIAL;
388 revalidateDiskEntry(object);
389 notifyObject(object);
390 return object;
393 static int
394 objectAddChunk(ObjectPtr object, const char *data, int offset, int plen)
396 int i = offset / CHUNK_SIZE;
397 int rc;
399 assert(offset % CHUNK_SIZE == 0);
400 assert(plen <= CHUNK_SIZE);
402 if(object->numchunks <= i) {
403 rc = objectSetChunks(object, i + 1);
404 if(rc < 0)
405 return -1;
408 lockChunk(object, i);
410 if(object->chunks[i].data == NULL) {
411 object->chunks[i].data = get_chunk();
412 if(object->chunks[i].data == NULL)
413 goto fail;
416 if(object->chunks[i].size >= plen) {
417 unlockChunk(object, i);
418 return 0;
421 if(object->size < offset + plen)
422 object->size = offset + plen;
423 object->chunks[i].size = plen;
424 memcpy(object->chunks[i].data, data, plen);
425 unlockChunk(object, i);
426 return 0;
428 fail:
429 unlockChunk(object, i);
430 return -1;
433 static int
434 objectAddChunkEnd(ObjectPtr object, const char *data, int offset, int plen)
436 int i = offset / CHUNK_SIZE;
437 int rc;
439 assert(offset % CHUNK_SIZE != 0 &&
440 offset % CHUNK_SIZE + plen <= CHUNK_SIZE);
442 if(object->numchunks <= i) {
443 rc = objectSetChunks(object, i + 1);
444 if(rc < 0)
445 return -1;
448 lockChunk(object, i);
450 if(object->chunks[i].data == NULL)
451 object->chunks[i].data = get_chunk();
452 if(object->chunks[i].data == NULL)
453 goto fail;
455 if(offset > object->size) {
456 goto fail;
459 if(object->chunks[i].size < offset % CHUNK_SIZE) {
460 goto fail;
463 if(object->size < offset + plen)
464 object->size = offset + plen;
465 object->chunks[i].size = offset % CHUNK_SIZE + plen;
466 memcpy(object->chunks[i].data + (offset % CHUNK_SIZE),
467 data, plen);
469 unlockChunk(object, i);
470 return 0;
472 fail:
473 unlockChunk(object, i);
474 return -1;
478 objectAddData(ObjectPtr object, const char *data, int offset, int len)
480 int rc;
482 do_log(D_OBJECT_DATA, "Adding data to 0x%lx (%d) at %d: %d bytes\n",
483 (unsigned long)object, object->length, offset, len);
485 if(len == 0)
486 return 1;
488 if(object->length >= 0) {
489 if(offset + len > object->length) {
490 do_log(L_ERROR,
491 "Inconsistent object length (%d, should be at least %d).\n",
492 object->length, offset + len);
493 object->length = offset + len;
497 object->flags &= ~OBJECT_FAILED;
499 if(offset + len >= object->numchunks * CHUNK_SIZE) {
500 rc = objectSetChunks(object, (offset + len - 1) / CHUNK_SIZE + 1);
501 if(rc < 0) {
502 return -1;
506 if(offset % CHUNK_SIZE != 0) {
507 int plen = CHUNK_SIZE - offset % CHUNK_SIZE;
508 if(plen >= len)
509 plen = len;
510 rc = objectAddChunkEnd(object, data, offset, plen);
511 if(rc < 0) {
512 return -1;
514 offset += plen;
515 data += plen;
516 len -= plen;
519 while(len > 0) {
520 int plen = (len >= CHUNK_SIZE) ? CHUNK_SIZE : len;
521 rc = objectAddChunk(object, data, offset, plen);
522 if(rc < 0) {
523 return -1;
525 offset += plen;
526 data += plen;
527 len -= plen;
530 return 1;
533 void
534 objectPrintf(ObjectPtr object, int offset, const char *format, ...)
536 char *buf;
537 int rc;
539 va_list args;
540 va_start(args, format);
541 buf = vsprintf_a(format, args);
542 va_end(args);
544 if(buf == NULL) {
545 abortObject(object, 500, internAtom("Couldn't allocate string"));
546 return;
549 rc = objectAddData(object, buf, offset, strlen(buf));
550 free(buf);
551 if(rc < 0)
552 abortObject(object, 500, internAtom("Couldn't add data to object"));
555 int
556 objectHoleSize(ObjectPtr object, int offset)
558 int size = 0, i;
560 if(offset < 0 || offset / CHUNK_SIZE >= object->numchunks)
561 return -1;
563 if(offset % CHUNK_SIZE != 0) {
564 if(object->chunks[offset / CHUNK_SIZE].size > offset % CHUNK_SIZE)
565 return 0;
566 else {
567 size += CHUNK_SIZE - offset % CHUNK_SIZE;
568 offset += CHUNK_SIZE - offset % CHUNK_SIZE;
569 if(offset < 0) {
570 /* Overflow */
571 return -1;
576 for(i = offset / CHUNK_SIZE; i < object->numchunks; i++) {
577 if(object->chunks[i].size == 0)
578 size += CHUNK_SIZE;
579 else
580 break;
582 if(i >= object->numchunks)
583 return -1;
584 return size;
588 /* Returns 2 if the data is wholly in memory, 1 if it's available on disk */
590 objectHasData(ObjectPtr object, int from, int to)
592 int first = from / CHUNK_SIZE;
593 int last = to / CHUNK_SIZE;
594 int i, upto;
596 if(to < 0) {
597 if(object->length >= 0)
598 to = object->length;
599 else
600 return 0;
603 if(from >= to)
604 return 2;
606 if(to > object->size) {
607 upto = to;
608 goto disk;
611 if(last > object->numchunks ||
612 object->chunks[last].size > to % CHUNK_SIZE) {
613 upto = to;
614 goto disk;
617 for(i = last - 1; i >= first; i--) {
618 if(object->chunks[i].size < CHUNK_SIZE) {
619 upto = (i + 1) * CHUNK_SIZE;
620 goto disk;
624 return 2;
626 disk:
627 if(object->flags & OBJECT_DISK_ENTRY_COMPLETE)
628 return 1;
630 if(diskEntrySize(object) >= upto)
631 return 1;
633 return 0;
636 void
637 destroyObject(ObjectPtr object)
639 int i;
641 assert(object->refcount == 0 && !object->requestor);
642 assert(!object->condition.handlers &&
643 (object->flags & OBJECT_INPROGRESS) == 0);
645 if(object->disk_entry)
646 destroyDiskEntry(object, 0);
648 if(object->flags & OBJECT_PUBLIC) {
649 privatiseObject(object, 0);
650 } else {
651 object->type = -1;
652 if(object->message) releaseAtom(object->message);
653 if(object->key) free(object->key);
654 if(object->headers) releaseAtom(object->headers);
655 if(object->etag) free(object->etag);
656 if(object->via) releaseAtom(object->via);
657 for(i = 0; i < object->numchunks; i++) {
658 assert(!object->chunks[i].locked);
659 if(object->chunks[i].data)
660 dispose_chunk(object->chunks[i].data);
661 object->chunks[i].data = NULL;
662 object->chunks[i].size = 0;
664 if(object->chunks) free(object->chunks);
665 privateObjectCount--;
666 free(object);
670 void
671 privatiseObject(ObjectPtr object, int linear)
673 int i, h;
674 if(!(object->flags & OBJECT_PUBLIC)) {
675 if(linear)
676 object->flags |= OBJECT_LINEAR;
677 return;
680 if(object->disk_entry)
681 destroyDiskEntry(object, 0);
682 object->flags &= ~OBJECT_PUBLIC;
684 for(i = 0; i < object->numchunks; i++) {
685 if(object->chunks[i].locked)
686 break;
687 if(object->chunks[i].data) {
688 object->chunks[i].size = 0;
689 dispose_chunk(object->chunks[i].data);
690 object->chunks[i].data = NULL;
694 h = hash(object->type, object->key, object->key_size,
695 log2ObjectHashTableSize);
696 assert(objectHashTable[h] == object);
697 objectHashTable[h] = NULL;
699 if(object->previous)
700 object->previous->next = object->next;
701 if(object_list == object)
702 object_list = object->next;
703 if(object->next)
704 object->next->previous = object->previous;
705 if(object_list_end == object)
706 object_list_end = object->previous;
707 object->previous = NULL;
708 object->next = NULL;
710 publicObjectCount--;
711 privateObjectCount++;
713 if(object->refcount == 0)
714 destroyObject(object);
715 else {
716 if(linear)
717 object->flags |= OBJECT_LINEAR;
721 void
722 abortObject(ObjectPtr object, int code, AtomPtr message)
724 int i;
726 assert(code != 0);
728 object->flags &= ~(OBJECT_INITIAL | OBJECT_VALIDATING);
729 object->flags |= OBJECT_ABORTED;
730 object->code = code;
731 if(object->message) releaseAtom(object->message);
732 object->message = message;
733 object->length = 0;
734 object->date = object->age;
735 object->expires = object->age;
736 object->last_modified = -1;
737 if(object->etag) free(object->etag);
738 object->etag = NULL;
739 if(object->headers) releaseAtom(object->headers);
740 object->headers = NULL;
741 object->size = 0;
742 for(i = 0; i < object->numchunks; i++) {
743 if(object->chunks[i].data) {
744 if(!object->chunks[i].locked) {
745 dispose_chunk(object->chunks[i].data);
746 object->chunks[i].data = NULL;
747 object->chunks[i].size = 0;
751 privatiseObject(object, 0);
754 void
755 supersedeObject(ObjectPtr object)
757 object->flags |= OBJECT_SUPERSEDED;
758 destroyDiskEntry(object, 1);
759 privatiseObject(object, 0);
760 notifyObject(object);
763 void
764 notifyObject(ObjectPtr object)
766 retainObject(object);
767 signalCondition(&object->condition);
768 releaseObject(object);
772 discardObjectsHandler(TimeEventHandlerPtr event)
774 return discardObjects(0, 0);
777 void
778 writeoutObjects(int all)
780 ObjectPtr object = object_list;
781 int bytes;
782 int objects;
783 int n;
785 if(diskIsClean) return;
787 objects = 0;
788 bytes = 0;
789 while(object) {
790 do {
791 if(!all) {
792 if(objects >= maxObjectsWhenIdle ||
793 bytes >= maxWriteoutWhenIdle) {
794 if(workToDo()) return;
795 objects = 0;
796 bytes = 0;
799 n = writeoutToDisk(object, -1, all ? -1 : maxWriteoutWhenIdle);
800 bytes += n;
801 } while(!all && n == maxWriteoutWhenIdle);
802 objects++;
803 object = object->next;
805 diskIsClean = 1;
809 discardObjects(int all, int force)
811 ObjectPtr object;
812 int i;
813 static int in_discardObjects = 0;
814 TimeEventHandlerPtr event;
816 if(in_discardObjects)
817 return 0;
819 in_discardObjects = 1;
821 if(all || force || used_chunks >= CHUNKS(chunkHighMark) ||
822 publicObjectCount >= publicObjectLowMark ||
823 publicObjectCount + privateObjectCount >= objectHighMark) {
824 object = object_list_end;
825 while(object &&
826 (all || force || used_chunks >= CHUNKS(chunkLowMark))) {
827 if(force || ((object->flags & OBJECT_PUBLIC) &&
828 object->numchunks > CHUNKS(chunkLowMark) / 4)) {
829 int j;
830 for(j = 0; j < object->numchunks; j++) {
831 if(object->chunks[j].locked) {
832 break;
834 if(object->chunks[j].size < CHUNK_SIZE) {
835 continue;
837 writeoutToDisk(object, (j + 1) * CHUNK_SIZE, -1);
838 dispose_chunk(object->chunks[j].data);
839 object->chunks[j].data = NULL;
840 object->chunks[j].size = 0;
841 i++;
844 object = object->previous;
847 i = 0;
848 object = object_list_end;
849 while(object &&
850 (all || force ||
851 used_chunks - i > CHUNKS(chunkLowMark) ||
852 used_chunks > CHUNKS(chunkCriticalMark) ||
853 publicObjectCount > publicObjectLowMark)) {
854 ObjectPtr next_object = object->previous;
855 if(object->refcount == 0) {
856 i += object->numchunks;
857 writeoutToDisk(object, object->size, -1);
858 privatiseObject(object, 0);
859 } else if(all || force) {
860 writeoutToDisk(object, object->size, -1);
861 destroyDiskEntry(object, 0);
863 object = next_object;
866 object = object_list_end;
867 if(force || used_chunks > CHUNKS(chunkCriticalMark)) {
868 if(used_chunks > CHUNKS(chunkCriticalMark)) {
869 do_log(L_WARN,
870 "Short on chunk memory -- "
871 "attempting to punch holes "
872 "in the middle of objects.\n");
874 while(object &&
875 (force || used_chunks > CHUNKS(chunkCriticalMark))) {
876 if(force || (object->flags & OBJECT_PUBLIC)) {
877 int j;
878 for(j = object->numchunks - 1; j >= 0; j--) {
879 if(object->chunks[j].locked)
880 continue;
881 if(object->chunks[j].size < CHUNK_SIZE)
882 continue;
883 writeoutToDisk(object, (j + 1) * CHUNK_SIZE, -1);
884 dispose_chunk(object->chunks[j].data);
885 object->chunks[j].data = NULL;
886 object->chunks[j].size = 0;
889 object = object->previous;
892 event = scheduleTimeEvent(2, discardObjectsHandler, 0, NULL);
893 if(event) {
894 objectExpiryScheduled = 1;
895 } else {
896 objectExpiryScheduled = 0;
897 do_log(L_ERROR, "Couldn't schedule object expiry.\n");
899 } else {
900 objectExpiryScheduled = 0;
903 if(all) {
904 if(privateObjectCount + publicObjectCount != 0) {
905 do_log(L_WARN,
906 "Discarded all objects, "
907 "%d + %d objects left (%d chunks and %d atoms used).\n",
908 publicObjectCount, privateObjectCount,
909 used_chunks, used_atoms);
910 } else if(used_chunks != 0) {
911 do_log(L_WARN,
912 "Discarded all objects, "
913 "%d chunks and %d atoms left.\n",
914 used_chunks, used_atoms);
916 diskIsClean = 1;
919 in_discardObjects = 0;
920 return 1;
923 CacheControlRec no_cache_control = {0, -1, -1, -1, -1};
926 objectIsStale(ObjectPtr object, CacheControlPtr cache_control)
928 int stale = 0x7FFFFFFF;
929 int flags;
930 int max_age, s_maxage;
931 time_t date;
933 if(object->flags & OBJECT_INITIAL)
934 return 0;
936 if(object->date >= 0)
937 date = object->date;
938 else if(object->age >= 0)
939 date = object->age;
940 else
941 date = current_time.tv_sec;
943 if(cache_control == NULL)
944 cache_control = &no_cache_control;
945 flags = object->cache_control | cache_control->flags;
947 if(cache_control->max_age >= 0) {
948 if(object->max_age >= 0)
949 max_age = MIN(cache_control->max_age, object->max_age);
950 else
951 max_age = cache_control->max_age;
952 } else
953 max_age = object->max_age;
955 if(cache_control->s_maxage >= 0) {
956 if(object->s_maxage >= 0)
957 s_maxage = MIN(cache_control->s_maxage, object->s_maxage);
958 else
959 s_maxage = cache_control->s_maxage;
960 } else
961 s_maxage = object->s_maxage;
963 if(max_age >= 0)
964 stale = MIN(stale, object->age + max_age);
966 if(cacheIsShared && s_maxage >= 0)
967 stale = MIN(stale, object->age + s_maxage);
969 if(object->expires >= 0 || object->max_age >= 0)
970 stale = MIN(stale, object->age + maxExpiresAge);
971 else
972 stale = MIN(stale, object->age + maxAge);
974 /* RFC 2616 14.9.3: server-side max-age overrides expires */
976 if(object->expires >= 0 && object->max_age < 0) {
977 /* This protects against clock skew */
978 stale = MIN(stale, object->age + object->expires - date);
981 if(object->expires < 0 && object->max_age < 0) {
982 /* No server-side information -- heuristic expiration */
983 if(object->last_modified >= 0)
984 /* Again, take care of clock skew */
985 stale = MIN(stale,
986 object->age +
987 (date - object->last_modified) * maxAgeFraction);
988 else
989 stale = MIN(stale, object->age + maxNoModifiedAge);
992 if(!(flags & CACHE_MUST_REVALIDATE) &&
993 !(cacheIsShared && (flags & CACHE_PROXY_REVALIDATE))) {
994 /* Client side can relax transparency */
995 if(cache_control->min_fresh >= 0) {
996 if(cache_control->max_stale >= 0)
997 stale = MIN(stale - cache_control->min_fresh,
998 stale + cache_control->max_stale);
999 else
1000 stale = stale - cache_control->min_fresh;
1001 } else if(cache_control->max_stale >= 0) {
1002 stale = stale + cache_control->max_stale;
1006 return current_time.tv_sec > stale;
1010 objectMustRevalidate(ObjectPtr object, CacheControlPtr cache_control)
1012 int flags;
1014 if(cache_control == NULL)
1015 cache_control = &no_cache_control;
1016 if(object)
1017 flags = object->cache_control | cache_control->flags;
1018 else
1019 flags = cache_control->flags;
1021 if(flags & (CACHE_NO | CACHE_NO_HIDDEN | CACHE_NO_STORE))
1022 return 1;
1024 if(cacheIsShared && (flags & CACHE_PRIVATE))
1025 return 1;
1027 if(!mindlesslyCacheVary && (flags & CACHE_VARY))
1028 return 1;
1030 if(dontCacheCookies && (flags & CACHE_COOKIE))
1031 return 1;
1033 if(object)
1034 return objectIsStale(object, cache_control);
1036 return 0;