source: branches/portable/disk-cache.lisp@ 31

Last change on this file since 31 was 17, checked in by wws, 10 years ago

Can you say little-endian? Thought you could.

Don't use 16-bit access for disk cache buffers. Force big-endian storage in the file.

  • Property svn:eol-style set to native
File size: 36.0 KB
Line 
1;;;-*- Mode: Lisp; Package: WOOD -*-
2
3;;;;;;;;;;;;;;;;;;;;;;;;;;
4;;
5;; disk-cache.lisp
6;; Code to support a cached byte I/O stream.
7;;
8;; Portions Copyright © 2006 Clozure Associates and Anvita eReference (www.Anvita.info)
9;; Copyright © 1996 Digitool, Inc.
10;; Copyright © 1992-1995 Apple Computer, Inc.
11;; All rights reserved.
12;; Permission is given to use, copy, and modify this software provided
13;; that Digitool is given credit in all derivative works.
14;; This software is provided "as is". Digitool makes no warranty or
15;; representation, either express or implied, with respect to this software,
16;; its quality, accuracy, merchantability, or fitness for a particular
17;; purpose.
18;;
19
20;;;;;;;;;;;;;;;;;;;;;;;;;;
21;;
22;; Modification History
23;;
24;; 02/01/06 gz LispWorks port
25;; ------------- 0.96
26;; ------------- 0.95
27;; ------------- 0.94
28;; 03/27/96 bill Dylan changes add a :read-only-p keyword to open-disk-cache
29;; ------------- 0.93
30;; 05/31/95 bill Shared swapping space:
31;; Move the page-size, max-pages, pages & locked-pages slots from the
32;; disk-cache structure to the new shared-buffer structure. disk-cache
33;; gets a new shared-buffer slot to hold a shared-buffer instance.
34;; cons-disk-page works with a NIL disk-cache arg.
35;; print-disk-page works if disk-page-stream is NIL.
36;; New shared-buffer-pool structure.
37;; New get-shared-buffer function, gets or allocates a shared-buffer of
38;; a particular page size from a shared-buffer-pool instance.
39;; open-disk-cache takes new shared-buffer & shared-buffer-pool keyword
40;; args. If shared-buffer-pool is specified, uses get-shared-buffer
41;; to get a shared-buffer. Otherwise, if shared-buffer is specified, uses
42;; it. Otherwise, conses up a new shared-buffer with the given page-size
43;; & swapping-space.
44;; close-disk-cache call remove-disk-cache-from-shared-buffer, a new function
45;; that removes all references to a disk-cache from the disk-page's in a shared-buffer.
46;; add-disk-pages adds the new pages to the disk-cache-shared-buffer.
47;; read-disk-page takes a new disk-cache argument and uses it to initialize the
48;; disk-page-disk-cache & disk-page-stream slots.
49;; flush-disk-page works if disk-page-disk-cache is NIL.
50;; get-disk-page, lock-page, unlock-page updated for using the disk-cache-shared-buffer.
51;; extend-disk-cache no longer calls add-disk-pages. It lets get-disk-page do so.
52;; 05/25/95 Moon New constant: $disk-page-flags_touched-bit, set when a page is
53;; referenced.
54;; New functions, disk-page-touched? & (setf disk-page-touched?), to access
55;; the $disk-page-flags_touched-bit.
56;; get-disk-page now uses a 1-bit clock algorithm instead of
57;; least-recently-swapped to determine which page to swap out.
58;; 05/25/95 bill *default-page-size* moves here from "persistent-heap.lisp".
59;; New parameter, *default-swapping-space*, is the default number
60;; of bytes to use for swapping space.
61;; New parameter, *big-io-buffers*, true if cl:open takes an
62;; :elements-per-buffer keyword arg.
63;; open-disk-cache defaults its page-size arg to
64;; *default-page-size* instead of 512. It errors if the page size
65;; is not at least 512. Doesn't pass an :external-format keyword
66;; arg to open unless one was passed in. If *big-io-buffers* is
67;; true, passes the page-size as the :elements-per-buffer keyword
68;; arg to open.
69;; extend-disk-cache takes a new, optional, extend-file? arg. If
70;; true, calls set-minimum-file-length to extend the length of the file.
71;; ------------- 0.9
72;; 11/17/95 bill poor man's transactions.
73;; open-disk-cache takes an :initial-transaction-p keyword.
74;; If nil (NOT the default), errors on any disk writes that
75;; happen outside of a start-disk-cache-transaction/commit-disk-cache-transaction
76;; pair.
77;; 11/03/94 ows open-disk-cache takes a mac-file-creator keyword,
78;; which it passes on to open.
79;; 10/28/94 Moon Change without-interrupts to with-databases-locked.
80;; Remove interlocking from get-disk-page; callers must.
81;; Add comment "Must be called inside with-databases-locked"
82;; to with-locked-page.
83;; 09/21/94 bill without-interrupts as necessary for interlocking
84;; 07/26/94 bill get-disk-page allocates a new page if all the pages
85;; are locked. Hence, it can't fail unless out of memory.
86;; ------------- 0.8
87;; 03/27/93 bill with-open-disk-cache
88;; ------------- 0.6
89;; ------------- 0.5
90;; 07/09/92 bill Don't extend the file until flushing a page requires it.
91;; Keep a lock count, not just a bit.
92;; 03/05/92 bill New file
93;;
94
95;;;;;;;;;;;;;;;;;;;;;;;;;;
96;;
97;; To do:
98;;
99;; with-databases-locked in just the right places.
100;; Add a journaling option.
101;; Multi-user support.
102;;
103
104(in-package :wood)
105
106(export '(open-disk-cache close-disk-cache disk-cache-size
107 get-disk-page mark-page-modified extend-disk-cache))
108
109;;;;;;;;;;;;;
110;;
111;; (open-disk-cache filename &key shared-p page-size max-pages
112;; if-exists if-does-not-exist)
113;;
114;; filename string or pathname
115;; shared-p boolean. Open for shared I/O if specified and true.
116;; page-size default: 512
117;; max-pages default: 200
118;; if-exists nil, :error, :supersede, or :overwrite.
119;; Default: :overwrite
120;; if-does-not-exist Same as for OPEN. default: :error.
121;;
122;; returns one value, a DISK-CACHE structure
123
124;;;;;;;;;;;;;
125;;
126;; (close-disk-cache disk-cache)
127;;
128;; Flushes dirty pages and closes the stream for the given disk-cache.
129
130;;;;;;;;;;;;;
131;;
132;; (disk-cache-size disk-cache)
133;;
134;; Return the number of bytes in the file
135
136;;;;;;;;;;;;;
137;;
138;; (get-disk-page disk-cache address &optional modify-p)
139;;
140;; disk-cache DISK-CACHE structure, as returned from OPEN-DISK-CACHE.
141;; address fixnum. the address from/to you wish to I/O
142;; modify-p boolean. True if you plan to write. Default: nil.
143;;
144;; returns four values:
145;; 1) array an array of type (array (signed-byte 8)) containing the byte
146;; at address
147;; 2) offset fixnum. The offset in the array for the byte at address.
148;; 3) length fixnum. The number of bytes of valid data in array at offset.
149;; Will be (- page-size (mod address page-size))
150;; unless the page is the last one or later.
151;; 4) page a disk-page structure that can be passed to mark-page-modified
152
153;;;;;;;;;;;;;
154;;
155;; (mark-page-modified disk-page)
156;;
157;; disk-page DISK-PAGE structure as returned in the fourth value from
158;; GET-DISK-PAGE.
159;;
160;; Sometimes you don't know in advance whether you'll modify a page.
161;;
162;; Returns true if the page was not already marked as modified, NIL
163;; otherwise.
164
165;;;;;;;;;;;;;
166;;
167;; (extend-disk-cache disk-cache new-size)
168;;
169;; new-size the new size of the file in bytes.
170;; If smaller than the current size, this is a NOP.
171
172
173(defstruct (shared-buffer (:constructor cons-shared-buffer (page-size page-count max-pages pages))
174 (:print-function print-shared-buffer))
175 (page-size 512) ; size of a disk-page in bytes
176 page-count ; number of disk pages
177 max-pages ; user's maximum
178 pages ; head of the disk-page chain
179 locked-pages ; head of locked pages chain
180 users ; a list of disk-cache instances
181 )
182
183(defun print-shared-buffer (shared-buffer stream level)
184 (declare (ignore level))
185 (print-unreadable-object (shared-buffer stream :type t :identity t)
186 (format stream "~s ~s/~s"
187 (shared-buffer-page-size shared-buffer)
188 (shared-buffer-page-count shared-buffer)
189 (shared-buffer-max-pages shared-buffer))))
190
191(defparameter *default-page-size* 512)
192(defparameter *default-swapping-space* (* 100 1024))
193
194(defun make-shared-buffer (&key (page-size *default-page-size*)
195 (swapping-space *default-swapping-space*)
196 max-pages)
197 (if (null max-pages)
198 (setq max-pages (ceiling swapping-space page-size))
199 (setq swapping-space (* max-pages page-size)))
200 (unless (>= page-size 512)
201 (error "Page size must be at least 512"))
202 (unless (eql page-size (expt 2 (1- (integer-length page-size))))
203 (error "page-size must be a power of 2"))
204 (cons-shared-buffer
205 page-size 0 max-pages nil))
206
207(defstruct (shared-buffer-pool (:constructor cons-shared-buffer-pool
208 (swapping-space page-size auxiliary-swapping-space)))
209 (swapping-space *default-swapping-space*)
210 (page-size *default-page-size*)
211 (auxiliary-swapping-space *default-swapping-space*)
212 buffers)
213
214(defun make-shared-buffer-pool (&key (swapping-space *default-swapping-space*)
215 (page-size *default-page-size*)
216 (auxiliary-swapping-space
217 (min swapping-space *default-swapping-space*)))
218 (cons-shared-buffer-pool swapping-space page-size auxiliary-swapping-space))
219
220(defun get-shared-buffer (pool page-size)
221 (or (find page-size (shared-buffer-pool-buffers pool) :key 'shared-buffer-page-size)
222 (let* ((swapping-space (if (eql page-size (shared-buffer-pool-page-size pool))
223 (shared-buffer-pool-swapping-space pool)
224 (shared-buffer-pool-auxiliary-swapping-space pool)))
225 (buffer (make-shared-buffer :page-size page-size
226 :swapping-space swapping-space)))
227 (push buffer (shared-buffer-pool-buffers pool))
228 buffer)))
229
230(defstruct (disk-cache (:print-function print-disk-cache))
231 stream ; a stream to a file
232 size ; the length of the file
233 (page-size 512) ; size of a disk-page in bytes
234 (mask -512) ; address mask
235 shared-buffer ; a shared-buffer instance
236 page-hash ; page-address -> disk-page structure
237 dirty-pages ; head of the dirty page chain
238 log ; a LOG structure: see "recovery.lisp"
239 write-hook ; hook to call when a page is written to disk
240 file-eof ; current EOF on disk
241 transaction ; current transaction (just a counter for now)
242 )
243
244(defun print-disk-cache (disk-cache stream level)
245 (declare (ignore level))
246 (print-unreadable-object (disk-cache stream :type t :identity t)
247 (prin1 (pathname (disk-cache-stream disk-cache)) stream)))
248
249(defun disk-cache-read-only-p (disk-cache)
250 (eql (stream-direction (disk-cache-stream disk-cache))
251 :input))
252
253(defstruct (disk-page (:print-function print-disk-page) (:constructor cons-disk-page))
254 disk-cache ; back pointer
255 stream ; the stream (did you guess?)
256 address ; file address of base of this page
257 (flags 0) ; bit 0 = dirty, bit 1 = touched
258 (size 0) ; actual size (smaller for last page)
259 next ; next disk-page in the chain
260 prev ; previous disk-page in the chain
261 next-dirty ; next dirty page
262 prev-dirty ; previous dirty page
263 data ; an (unsigned-byte 8) array
264 (lock-count 0)) ; non-zero means locked that many times.
265
266(defconstant $disk-page-flags_dirty-bit 0)
267(defconstant $disk-page-flags_touched-bit 1)
268
269(defun disk-page-dirty (disk-page)
270 (logbitp $disk-page-flags_dirty-bit
271 (the fixnum (disk-page-flags disk-page))))
272
273(defun (setf disk-page-dirty) (value disk-page)
274 (with-databases-locked
275 (setf (disk-page-flags disk-page)
276 (if value
277 (%bitset $disk-page-flags_dirty-bit (disk-page-flags disk-page))
278 (%bitclr $disk-page-flags_dirty-bit (disk-page-flags disk-page))))
279 (not (null value))))
280
281(declaim (inline disk-page-touched? (setf disk-page-touched?)))
282
283(defun disk-page-touched? (disk-page)
284 (declare (optimize (speed 3) (safety 0)))
285 (logbitp $disk-page-flags_touched-bit
286 (the fixnum (disk-page-flags disk-page))))
287
288;; Must be called inside with-databases-locked
289(defun (setf disk-page-touched?) (value disk-page)
290 (declare (optimize (speed 3) (safety 0)))
291 (setf (disk-page-flags disk-page)
292 (if value
293 (%bitset $disk-page-flags_touched-bit (the fixnum (disk-page-flags disk-page)))
294 (%bitclr $disk-page-flags_touched-bit (the fixnum (disk-page-flags disk-page)))))
295 value)
296
297(defun disk-page-locked (disk-page)
298 (let ((count (disk-page-lock-count disk-page)))
299 (unless (eql 0 count)
300 count)))
301
302(defun print-disk-page (disk-page stream level)
303 (declare (ignore level))
304 (let* ((disk-page-stream (disk-page-stream disk-page))
305 (path (if disk-page-stream (pathname disk-page-stream) :no-file)))
306 (print-unreadable-object (disk-page stream :type t :identity t)
307 (format stream "~s~@{ ~s~}"
308 (disk-page-address disk-page)
309 (disk-page-size disk-page)
310 (disk-page-dirty disk-page)
311 path))))
312
313(defun make-disk-page (disk-cache size)
314 (cons-disk-page :disk-cache disk-cache
315 :stream (and disk-cache (disk-cache-stream disk-cache))
316 :data (make-array size :element-type '(unsigned-byte 8))))
317
318(defvar *open-disk-caches* nil)
319
320; New code
321(defparameter *big-io-buffers*
322 #+ccl (not (null (find :elements-per-buffer (ccl::lfun-keyvect #'open))))
323 #+LispWorks nil)
324
325(defun open-disk-cache (filename &key shared-p read-only-p
326 (page-size *default-page-size* page-size-p)
327 max-pages
328 (swapping-space *default-swapping-space*)
329 shared-buffer
330 shared-buffer-pool
331 (if-exists :overwrite)
332 (if-does-not-exist :error)
333 write-hook
334 (initial-transaction-p t))
335 (when shared-buffer-pool
336 (setq shared-buffer (get-shared-buffer shared-buffer-pool page-size)))
337 (if shared-buffer
338 (let ((shared-buffer-page-size (shared-buffer-page-size shared-buffer)))
339 (when (and page-size-p (not (eql page-size shared-buffer-page-size)))
340 (error "Page size different from shared-buffer page size"))
341 (setq page-size shared-buffer-page-size))
342 (setq shared-buffer
343 (make-shared-buffer :page-size page-size
344 :max-pages max-pages
345 :swapping-space swapping-space)))
346 (setq max-pages (shared-buffer-max-pages shared-buffer))
347 (let ((mask (lognot (1- (expt 2 (1- (integer-length page-size)))))))
348 (let* ((stream (open filename
349 :direction (if read-only-p :input (if shared-p :shared :io))
350 :if-exists if-exists
351 :if-does-not-exist if-does-not-exist
352 :element-type '(unsigned-byte 8)
353 #+ccl :sharing #+ccl :external)))
354 (when stream
355 (let* ((size (file-length stream))
356 (disk-cache (make-disk-cache :stream stream
357 :size size
358 :file-eof size
359 :page-size page-size
360 :mask mask
361 :shared-buffer shared-buffer
362 ;remove :max-pages max-pages
363 :write-hook write-hook)))
364 #+wood-fixnum-addresses
365 (unless (fixnump size)
366 (error "File ~s is too large for this compilation of Wood~%~
367 Recompile Wood with :wood-fixnum-addresses removed from *features*"
368 filename))
369 (setf (disk-cache-page-hash disk-cache)
370 (make-disk-page-hash :size (min (ceiling size page-size) max-pages)
371 :page-size page-size))
372 (when initial-transaction-p
373 (setf (disk-cache-transaction disk-cache) 1))
374 (push disk-cache *open-disk-caches*)
375 (push disk-cache (shared-buffer-users shared-buffer))
376 disk-cache)))))
377
378(defmacro with-open-disk-cache ((disk-cache filename &rest options) &body body)
379 `(let ((,disk-cache (open-disk-cache ,filename ,@options)))
380 (unwind-protect
381 (progn ,@body)
382 (close-disk-cache ,disk-cache))))
383
384(defun make-linked-disk-pages (disk-cache page-size page-count &optional file-length)
385 (when file-length
386 (setq page-count (max 1 (min page-count
387 (floor (+ file-length page-size -1)
388 page-size)))))
389 (let (page last-page)
390 (dotimes (i page-count)
391 (let ((new-page (make-disk-page disk-cache page-size)))
392 (setf (disk-page-next new-page) page)
393 (if page
394 (setf (disk-page-prev page) new-page)
395 (setq last-page new-page))
396 (setq page new-page)))
397 (setf (disk-page-next last-page) page
398 (disk-page-prev page) last-page)
399 (values page page-count)))
400
401(defun add-disk-pages (disk-cache count)
402 (let* ((shared-buffer (disk-cache-shared-buffer disk-cache))
403 (old-first-page (shared-buffer-pages shared-buffer))
404 (new-first-page (make-linked-disk-pages
405 disk-cache
406 (disk-cache-page-size disk-cache)
407 count)))
408 (when old-first-page
409 (let ((old-last-page (disk-page-prev old-first-page))
410 (new-last-page (disk-page-prev new-first-page)))
411 (setf (disk-page-next new-last-page) old-first-page
412 (disk-page-prev old-first-page) new-last-page
413 (disk-page-next old-last-page) new-first-page
414 (disk-page-prev new-first-page) old-last-page)))
415 (setf (shared-buffer-pages shared-buffer) new-first-page)
416 (incf (shared-buffer-page-count shared-buffer) count)))
417
418(defun close-disk-cache (disk-cache)
419 (flush-disk-cache disk-cache) ; work interruptably
420 (with-databases-locked
421 (flush-disk-cache disk-cache) ; make sure
422 (remove-disk-cache-from-shared-buffer (disk-cache-shared-buffer disk-cache) disk-cache)
423 (close (disk-cache-stream disk-cache))
424 (setq *open-disk-caches* (delq disk-cache *open-disk-caches* 1))
425 (setf (disk-cache-page-hash disk-cache) nil)))
426
427(defun remove-disk-cache-from-shared-buffer (shared-buffer disk-cache)
428 (if (null (setf (shared-buffer-users shared-buffer)
429 (delete disk-cache (shared-buffer-users shared-buffer) :test 'eq)))
430 (setf (shared-buffer-page-count shared-buffer) 0
431 (shared-buffer-pages shared-buffer) nil
432 (shared-buffer-locked-pages shared-buffer) nil)
433 (let ((page-hash (disk-cache-page-hash disk-cache)))
434 (when page-hash
435 (let* ((locked-pages nil)
436 (mapper #'(lambda (address page)
437 (unless (eq disk-cache (disk-page-disk-cache page))
438 (error "page in disk-page-hash doesn't belong to disk-cache"))
439 (unless (eql 0 (disk-page-lock-count page))
440 (push page locked-pages)
441 (loop
442 (unless (unlock-page page) (return))))
443 (disk-page-remhash address page-hash)
444 (setf (disk-page-disk-cache page) nil
445 (disk-page-address page) nil))))
446 (declare (dynamic-extent mapper))
447 (disk-page-maphash mapper page-hash)
448 (when locked-pages
449 (cerror "Continue" "Locked pages: ~s" locked-pages)))))))
450
451(defun flush-disk-cache (disk-cache)
452 (unless (disk-cache-read-only-p disk-cache)
453 (loop
454 (with-databases-locked
455 (let* ((page (disk-cache-dirty-pages disk-cache)))
456 (unless page (return))
457 (flush-disk-page page))))
458 (with-databases-locked
459 (finish-output (disk-cache-stream disk-cache)))))
460
461(defun read-disk-page (disk-cache disk-page address)
462 (flush-disk-page disk-page)
463 (when (> (the fixnum (disk-page-lock-count disk-page)) 0)
464 (error "Attempt to read locked page"))
465 (setf (disk-page-disk-cache disk-page) disk-cache
466 (disk-page-stream disk-page) (disk-cache-stream disk-cache)
467 (disk-page-address disk-page) address)
468 (let* ((size (disk-cache-size disk-cache))
469 (file-eof (disk-cache-file-eof disk-cache))
470 (page-size (min (disk-cache-page-size disk-cache) (- size address))))
471 (when (> file-eof address)
472 (stream-read-bytes (disk-page-stream disk-page)
473 address
474 (disk-page-data disk-page)
475 0
476 page-size))
477 (setf (disk-page-size disk-page) page-size)))
478
479(defun flush-disk-page (disk-page)
480 (when (disk-page-dirty disk-page)
481 (let* ((disk-cache (disk-page-disk-cache disk-page))
482 (write-hook (and disk-cache (disk-cache-write-hook disk-cache))))
483 (when write-hook
484 (funcall write-hook disk-page))
485 (when (or (not write-hook) (disk-page-dirty disk-page)) ; write-hook may have flushed this page
486 (let* ((address (disk-page-address disk-page))
487 (size (disk-page-size disk-page))
488 (end-of-page (+ address size))
489 (stream (disk-page-stream disk-page)))
490 (when (> end-of-page (disk-cache-file-eof disk-cache))
491 (set-minimum-file-length stream end-of-page)
492 (setf (disk-cache-file-eof disk-cache) end-of-page))
493 (stream-write-bytes stream
494 address
495 (disk-page-data disk-page)
496 0
497 size))
498 (let* ((next (disk-page-next-dirty disk-page))
499 (prev (disk-page-prev-dirty disk-page)))
500 (if (eq next disk-page)
501 (setf next nil)
502 (setf (disk-page-next-dirty prev) next
503 (disk-page-prev-dirty next) prev))
504 (setf (disk-page-next-dirty disk-page) nil
505 (disk-page-prev-dirty disk-page) nil)
506 (when (eq disk-page (disk-cache-dirty-pages disk-cache))
507 (setf (disk-cache-dirty-pages disk-cache) next)))
508 (setf (disk-page-dirty disk-page) nil)))))
509
510; The caller must be inside of with-databases-locked, or the buffer returned
511; could be yanked out from under the caller.
512; 1-bit-clock page replacement algorithm.
513(defun get-disk-page (disk-cache address &optional modify-p)
514 (declare (optimize (speed 3)(safety 0)))
515 #+wood-fixnum-addresses
516 (unless (fixnump address)
517 (error "Address is not a fixnum"))
518 (locally
519 #+wood-fixnum-addresses (declare (fixnum address))
520 (let* ((hash (disk-cache-page-hash disk-cache))
521 (base-address (logand address (the fixnum (disk-cache-mask disk-cache))))
522 (page (disk-page-gethash base-address hash))
523 (offset (- address base-address))
524 (size 0))
525 #+wood-fixnum-addresses (declare (fixnum base-address))
526 (declare (fixnum offset size))
527 (block get-the-page
528 (if page
529 (setq size (disk-page-size page))
530 (let ((max-size (disk-cache-size disk-cache))
531 (shared-buffer (disk-cache-shared-buffer disk-cache)))
532 #+wood-fixnum-addresses (declare (fixnum max-size))
533 (when (>= address max-size)
534 (if (> address max-size)
535 (error "~s > size of ~s" address disk-cache)
536 (when (eql address base-address)
537 ; If the address is the beginning of a page, and the end of
538 ; the file, return a pointer off the end of the last page.
539 (setq base-address (logand (1- address) (disk-cache-mask disk-cache))
540 offset (- address base-address)
541 page (disk-page-gethash base-address hash))
542 (when page
543 (setq size (disk-page-size page))
544 (return-from get-the-page)))))
545 ; Keep adding pages till we max out.
546 (when (>= (shared-buffer-page-count shared-buffer)
547 (shared-buffer-max-pages shared-buffer))
548 (setq page (shared-buffer-pages shared-buffer)))
549 (unless page
550 (add-disk-pages disk-cache 1)
551 (setq page (shared-buffer-pages shared-buffer)))
552 ;; Here's the page replacement algorithm, one-bit clock algorithm
553 (loop ; while disk-page-touched?
554 (unless (disk-page-touched? page) (return))
555 (setf (disk-page-touched? page) nil)
556 (setq page (disk-page-next page)))
557 (setf (shared-buffer-pages shared-buffer) (disk-page-next page))
558 (let ((old-address (disk-page-address page)))
559 (when old-address
560 (disk-page-remhash
561 old-address (disk-cache-page-hash (disk-page-disk-cache page)))))
562 (setq size (read-disk-page disk-cache page base-address))
563 (setf (disk-page-gethash base-address hash) page))))
564 (setf (disk-page-touched? page) t)
565 (when modify-p (mark-page-modified page))
566 (values (disk-page-data page)
567 offset
568 (- size offset)
569 page))))
570
571(defvar *error-on-non-transaction-writes* t)
572
573; The caller must be inside of with-databases-locked
574(defun mark-page-modified (disk-page)
575 (declare (optimize (speed 3) (safety 0)))
576 (unless (disk-page-dirty disk-page)
577 ; Link this disk-page as the last one in the dirty cache.
578 (let* ((disk-cache (disk-page-disk-cache disk-page))
579 (dirty-pages (disk-cache-dirty-pages disk-cache)))
580 (when (disk-cache-read-only-p disk-cache)
581 (error "Modifying a read-only database"))
582 (when (and *error-on-non-transaction-writes*
583 (null (disk-cache-transaction disk-cache)))
584 (restart-case
585 (cerror "Let this write proceed"
586 "Write outside of transaction to ~s"
587 (or (disk-cache-pheap disk-cache) disk-cache))
588 (dont-repeat ()
589 :report (lambda (s)
590 (format s "Let this write proceed and don't warn in the future."))
591 (setq *error-on-non-transaction-writes* nil))))
592 (if dirty-pages
593 (let ((prev-dirty (disk-page-prev-dirty dirty-pages)))
594 (setf (disk-page-next-dirty prev-dirty) disk-page
595 (disk-page-prev-dirty disk-page) prev-dirty
596 (disk-page-next-dirty disk-page) dirty-pages
597 (disk-page-prev-dirty dirty-pages) disk-page))
598 (setf (disk-page-next-dirty disk-page) disk-page
599 (disk-page-prev-dirty disk-page) disk-page
600 (disk-cache-dirty-pages disk-cache) disk-page)))
601 (setf (disk-page-dirty disk-page) t)))
602
603; Return the lock count after locking.
604(defun lock-page (disk-page)
605 (let ((lock-count (disk-page-lock-count disk-page)))
606 (declare (fixnum lock-count))
607 (when (eql 0 lock-count)
608 (let* ((disk-cache (disk-page-disk-cache disk-page))
609 (shared-buffer (disk-cache-shared-buffer disk-cache))
610 (prev (disk-page-prev disk-page))
611 (next (disk-page-next disk-page))
612 (locked (shared-buffer-locked-pages shared-buffer))
613 (prev-locked (if locked (disk-page-prev locked) disk-page)))
614 (when (null locked)
615 (setf (shared-buffer-locked-pages shared-buffer) (setq locked disk-page)))
616 (setf (disk-page-next prev) next
617 (disk-page-prev next) prev
618 (disk-page-next prev-locked) disk-page
619 (disk-page-prev disk-page) prev-locked
620 (disk-page-prev locked) disk-page
621 (disk-page-next disk-page) locked)
622 (when (eq disk-page (shared-buffer-pages shared-buffer))
623 (setf (shared-buffer-pages shared-buffer)
624 (if (eq next disk-page) nil next)))))
625 (setf (disk-page-lock-count disk-page)
626 (the fixnum (1+ lock-count)))))
627
628; Return the lock count or NIL if the page unlocked when this returns.
629(defun unlock-page (disk-page)
630 (let ((count (disk-page-lock-count disk-page)))
631 (declare (fixnum count))
632 (when (not (eql 0 count))
633 (progn
634 (when (eql count 1)
635 (let* ((disk-cache (disk-page-disk-cache disk-page))
636 (shared-buffer (disk-cache-shared-buffer disk-cache))
637 (prev-locked (disk-page-prev disk-page))
638 (next-locked (disk-page-next disk-page))
639 (pages (shared-buffer-pages shared-buffer))
640 (prev (if pages (disk-page-prev pages) disk-page)))
641 (when (null pages)
642 (setf (shared-buffer-pages shared-buffer) (setq pages disk-page)))
643 (setf (disk-page-next prev-locked) next-locked
644 (disk-page-prev next-locked) prev-locked
645 (disk-page-next prev) disk-page
646 (disk-page-prev disk-page) prev
647 (disk-page-prev pages) disk-page
648 (disk-page-next disk-page) pages)
649 (when (eq disk-page (shared-buffer-locked-pages shared-buffer))
650 (setf (shared-buffer-locked-pages shared-buffer)
651 (if (eq next-locked disk-page) nil next-locked)))))
652 (setf (disk-page-lock-count disk-page) (decf count))
653 (and (not (eql 0 count)) count)))))
654
655
656;;; Must be called inside with-databases-locked
657(defmacro with-locked-page ((disk-page-or-disk-cache
658 &optional address modify-p array offset length page)
659 &body body &environment env)
660 (if address
661 (let (ignored-params)
662 (multiple-value-bind (body-tail decls) (parse-body body env nil)
663 (flet ((normalize (param &optional (ignoreable? t))
664 (or param
665 (let ((res (gensym)))
666 (if ignoreable? (push res ignored-params))
667 res))))
668 `(multiple-value-bind (,(normalize array) ,(normalize offset)
669 ,(normalize length) ,(setq page (normalize page nil)))
670 (get-disk-page ,disk-page-or-disk-cache ,address
671 ,@(if modify-p `(,modify-p)))
672 ,@(when ignored-params
673 `((declare (ignore ,@ignored-params))))
674 ,@decls
675 (with-locked-page (,page)
676 ,@body-tail)))))
677 (let ((page-var (gensym)))
678 `(let ((,page-var ,disk-page-or-disk-cache))
679 (unwind-protect
680 (progn
681 (lock-page ,page-var)
682 ,@body)
683 (unlock-page ,page-var))))))
684
685(defun lock-page-at-address (disk-cache address)
686 (with-databases-locked
687 (let ((page (nth-value 3 (get-disk-page disk-cache address))))
688 (values (lock-page page) page))))
689
690(defun unlock-page-at-address (disk-cache address)
691 (with-databases-locked
692 (let ((page (nth-value 3 (get-disk-page disk-cache address))))
693 (unlock-page page))))
694
695(defun extend-disk-cache (disk-cache new-size &optional extend-file?)
696 #+wood-fixnum-addresses
697 (unless (fixnump new-size)
698 (error "New size is not a fixnum"))
699 (with-databases-locked
700 (let ((size (disk-cache-size disk-cache)))
701 (when (> new-size size)
702 ; Update size of last page
703 (when (> size 0)
704 (let* ((page-address (logand (1- size) (disk-cache-mask disk-cache)))
705 (page (disk-page-gethash page-address (disk-cache-page-hash disk-cache))))
706 (when page
707 (setf (disk-page-size page)
708 (min (length (disk-page-data page)) (- new-size page-address))))))
709 ; increase the file size & install the new size
710 (when extend-file?
711 (extend-file-length (disk-cache-stream disk-cache) new-size))
712 (setf (disk-cache-size disk-cache) new-size)))))
713
714(defun flush-all-disk-caches ()
715 (dolist (dc *open-disk-caches*)
716 (if (eq :closed (stream-direction (disk-cache-stream dc)))
717 (setq *open-disk-caches* (delq dc *open-disk-caches*))
718 (flush-disk-cache dc))))
719
720(register-lisp-cleanup-function 'flush-all-disk-caches)
721
722;;;;;;;;;;;;;;;;;;;;;;;
723;;;
724;;; Transaction support
725;;;
726
727; Not used yet, maybe it's unnecessary.
728(defmacro with-disk-cache-transaction ((disk-cache) &body body)
729 (let ((thunk (gensym)))
730 `(let ((,thunk #'(lambda () ,@body)))
731 (declare (dynamic-extent ,thunk))
732 (funcall-with-disk-cache-transaction ,disk-cache ,thunk))))
733
734(defun funcall-with-disk-cache-transaction (disk-cache thunk)
735 (let ((transaction (start-disk-cache-transaction disk-cache))
736 (done nil))
737 (unwind-protect
738 (multiple-value-prog1
739 (funcall thunk)
740 (setq done t))
741 (if done
742 (commit-disk-cache-transaction transaction)
743 (abort-disk-cache-transaction transaction)))))
744
745; These are dummies for now. Just keep a counter of how many there are.
746(defun start-disk-cache-transaction (disk-cache)
747 (with-databases-locked
748 (setf (disk-cache-transaction disk-cache)
749 (+ 1 (or (disk-cache-transaction disk-cache) 0)))
750 disk-cache))
751
752(defun commit-disk-cache-transaction (transaction &optional (flush t))
753 (let ((disk-cache transaction))
754 (with-databases-locked
755 (let ((count (1- (disk-cache-transaction disk-cache))))
756 (setf (disk-cache-transaction disk-cache)
757 (if (eql count 0) nil count))))
758 (when flush
759 (with-databases-locked
760 (flush-disk-cache disk-cache)))))
761
762(defun abort-disk-cache-transaction (transaction &optional (flush t))
763 (commit-disk-cache-transaction transaction flush))
764
765
766#|
767(setq dc (open-disk-cache "temp.lisp"))
768
769; read a string from dc
770(defun rc (address size)
771 (declare (optimize (debug 3)))
772 (declare (special dc))
773 (let ((file-size (disk-cache-size dc)))
774 (setq size (max 0 (min size (- file-size address)))))
775 (let ((string (make-string size))
776 (index 0))
777 (loop
778 (when (<= size 0) (return string))
779 (multiple-value-bind (array array-index bytes) (get-disk-page dc address)
780 (dotimes (i (min size bytes))
781 (setf (schar string index) (code-char (aref array array-index)))
782 (incf index)
783 (incf array-index))
784 (decf size bytes)
785 (incf address bytes)))))
786
787(defun wc (string address)
788 (declare (special dc))
789 (let* ((length (length string))
790 (min-size (+ address length))
791 (index 0))
792 (when (> min-size (disk-cache-size dc))
793 (extend-disk-cache dc min-size))
794 (loop
795 (when (<= length 0) (return))
796 (multiple-value-bind (array array-index bytes) (get-disk-page dc address t)
797 (dotimes (i (min length bytes))
798 (declare (type (array (unsigned-byte 8)) array))
799 (setf (aref array array-index) (char-code (schar string index)))
800 (incf index)
801 (incf array-index))
802 (incf address bytes)
803 (decf length bytes)))))
804
805(close-disk-cache dc)
806
807|#
808;;; 1 3/10/94 bill 1.8d247
809;;; 2 7/26/94 Derek 1.9d027
810;;; 3 10/04/94 bill 1.9d071
811;;; 4 11/03/94 Moon 1.9d086
812;;; 5 11/05/94 kab 1.9d087
813;;; 2 2/18/95 RŽti 1.10d019
814;;; 3 3/23/95 bill 1.11d010
815;;; 4 6/02/95 bill 1.11d040
816;;; 5 8/01/95 bill 1.11d065
817;;; 6 8/18/95 bill 1.11d071
818;;; 7 8/25/95 Derek Derek and Neil's massive bug fix upload
Note: See TracBrowser for help on using the repository browser.