9696 // In the worst case, the framing of every data byte in
9797 // a mutation might be framed by a maxVarint-byte offset
9898 // and a 1-byte count. That's 1MB*8 = 8 MB.
99- // There needs to be headroom for the memory-length patch,
100- // so the minimum would be 8MB + 8 bytes, but we bump the
101- // patch block size to 16 MB instead.
99+ // There needs to be headroom for the memory-length and
100+ // disk-length patches, so the minimum would be 8MB + 16 bytes,
101+ // but we bump the patch block size to 16 MB instead.
102102 maxPatch = 16 << 20
103103)
104104
@@ -143,34 +143,47 @@ func (*devNull) Sync() error { return nil }
143143// to larger sizes using [Mem.Expand].
144144// (Shrinking the memory is not implemented.)
145145//
146- // Mutations can be grouped into atomic transactions using
146+ // Alongside the memory, a third disk file holds “disk-only” storage
147+ // accessed by [Mem.ReadDisk] and [Mem.WriteDisk].
148+ // This storage is not held in memory, only on disk.
149+ //
150+ // By default, each [Mem.Mutate] or [Mem.WriteDisk] is a separate
151+ // sequenced, atomic transaction. If a program crashes and the Mem is reopened,
152+ // the state of the memory is guaranteed to correspond to the state after
153+ // some arbitrary transaction.
154+ // The state of the disk is guaranteed to include all the writes through that
155+ // transaction, but it may also include later writes that were not recovered.
156+ //
157+ // Mutations and disk writes can be grouped into larger transactions using
147158// [Mem.BeginGroup] and [Mem.EndGroup].
148159//
149- // Calling [Mem.Sync] ensures that all modifications have been flushed
160+ // Calling [Mem.Sync] ensures that all transactions have been flushed
150161// to the underlying files, guaranteeing that a future [Open] will observe them.
151162//
152163// Calling [Mem.Close] closes the memory and leaves the mapping unreadable.
153164// Future accesses to the slice data returned by [Mem.Data] must be avoided.
154165// Those accesses will fault, meaning they crash the program unless
155166// [runtime/debug.SetPanicOnFault] has been used.
156167type Mem struct {
157- magic string
158- id [16 ]byte
159- tmp [frameExtra ]byte
160- ptmp [2 * binary .MaxVarintLen64 ]byte
161- span * span.Span
162- mem []byte
163- patched int // length of “patched” section of memory
164- current * writer
165- next * writer
166- disk File // disk-only (not in memory) storage
167- diskOff int64 // offset where user writes begin
168- patch []byte
169- group int // group start in patch, or -1 if not in group
170- groupData int // total group data
171- err error
172- closed bool
173- compact compact
168+ magic string
169+ id [16 ]byte
170+ tmp [frameExtra ]byte
171+ ptmp [2 * binary .MaxVarintLen64 ]byte
172+ span * span.Span
173+ mem []byte
174+ patched int // length of “patched” section of memory
175+ current * writer
176+ next * writer
177+ disk File // disk-only (not in memory) storage
178+ diskOff int64 // offset where user writes begin
179+ diskSize int64 // logical size of disk-only data
180+ diskPatched int64 // last diskSize recorded in patch
181+ patch []byte
182+ group int // group start in patch, or -1 if not in group
183+ groupData int // total group data
184+ err error
185+ closed bool
186+ compact compact
174187
175188 constantFlushing bool
176189
@@ -400,6 +413,7 @@ func open(magic string, file1, file2, disk File) (_ *Mem, err error) {
400413 if err := m .readFile (r1 ); err != nil {
401414 return nil , err
402415 }
416+ m .diskPatched = m .diskSize
403417 m .current = newWriter (r1 .file , r1 .seq )
404418 m .current .off = r1 .off
405419 m .next = newWriter (r2 .file , 0 )
@@ -551,9 +565,12 @@ func (m *Mem) replay(patch []byte) error {
551565 }
552566 if isDisk {
553567 // disk patch
554- if _ , err := m .disk .WriteAt (patch [:count ], m .diskOff + int64 (off )); err != nil {
555- return m .broken (err )
568+ if count > 0 {
569+ if _ , err := m .disk .WriteAt (patch [:count ], m .diskOff + int64 (off )); err != nil {
570+ return m .broken (err )
571+ }
556572 }
573+ m .diskSize = max (m .diskSize , int64 (off + count ))
557574 } else {
558575 // memory patch
559576 if off + count > uint64 (len (m .mem )) {
@@ -606,16 +623,30 @@ func (m *Mem) Offset(b []byte) (offset int, ok bool) {
606623 return int (off ), ok
607624}
608625
609- // BeginGroup starts an atomic mutation group.
610- // Expand and Mutate calls between Begin and [Mem.EndGroup]
611- // are guaranteed to be observed as an atomic unit
612- // upon reloading the memory: either they will all be
613- // present or none of them will be.
626+ // BeginGroup starts an atomic mutation group (a transaction).
627+ //
614628// Calls to BeginGroup must be followed eventually by a call to EndGroup
615629// and cannot be nested: it is an error to call BeginGroup twice
616630// without an intervening EndGroup.
617631//
618- // A group is limited to mutation of at most MaxGroupBytes bytes of mutated data.
632+ // For the Expand, Mutate, and WriteDisk calls between Begin and [Mem.EndGroup],
633+ // there are three possible outcomes after a Mem has been reloaded:
634+ //
635+ // 1. All of the effects will be observed (group reloaded).
636+ // 2. None of the effects will be observed (group not reloaded).
637+ // 3. No memory effects will be observed, and the WriteDisk calls
638+ // will not be observable by [Mem.DiskSize], but some or all of the
639+ // disk writes could still be observed by [Mem.ReadDisk].
640+ //
641+ // Considering only the memory, the group is an atomic unit,
642+ // either fully observed or not observed at all.
643+ // The disk is added to the group in a one-sided manner:
644+ // if the memory changes are observed, then all the disk changes are observed too,
645+ // but not the reverse: later disk changes (all of them or a subset of them) can be observed
646+ // without their corresponding memory changes.
647+ //
648+ // A group is limited to mutation of at most MaxGroupBytes bytes of data
649+ // modified by the combination of Mutate and WriteDisk.
619650func (m * Mem ) BeginGroup () error {
620651 if m .err != nil {
621652 return m .err
@@ -624,13 +655,17 @@ func (m *Mem) BeginGroup() error {
624655 return fmt .Errorf ("atomic mutation group already begun" )
625656 }
626657
627- // Patch buffer always has room to add an empty mutation
628- // at the end of the memory, to represent the most recent Expand.
629- // If the group grows too large, we will flush up to but not
630- // including the group, so add the empty mutation now.
658+ // Patch buffer always has room to add empty mutations
659+ // at the end of the memory and disk, to represent the most
660+ // recent Expand/WriteDisk. If the group grows too large,
661+ // we will flush up to but not including the group,
662+ // so add the empty mutations now.
631663 if err := m .addMemLenPatch (); err != nil {
632664 return err
633665 }
666+ if err := m .addDiskLenPatch (); err != nil {
667+ return err
668+ }
634669
635670 m .group = len (m .patch )
636671 m .groupData = 0
@@ -664,9 +699,9 @@ func (m *Mem) Mutate(dst, src []byte) error {
664699
665700// WriteDisk writes src to the disk-only file at offset off.
666701// It guarantees that on recovery after a crash,
667- // all disk writes that occurred before the latest recovered Mutate
668- // will be available for reading.
669- // (Disk writes that happened after that Mutate may or may not
702+ // all disk writes that occurred in or before
703+ // the latest recovered transaction will be available for reading.
704+ // (Disk writes that happened after that transaction may or may not
670705// be available for reading as well.)
671706func (m * Mem ) WriteDisk (src []byte , off int64 ) error {
672707 if m .err != nil {
@@ -680,6 +715,8 @@ func (m *Mem) WriteDisk(src []byte, off int64) error {
680715 if err != nil {
681716 return m .broken (err )
682717 }
718+ m .diskSize = max (m .diskSize , off + int64 (len (src )))
719+ m .diskPatched = max (m .diskPatched , off + int64 (len (src )))
683720 return nil
684721 })
685722}
@@ -689,6 +726,12 @@ func (m *Mem) ReadDisk(dst []byte, off int64) error {
689726 if m .err != nil {
690727 return m .err
691728 }
729+ if off < 0 || off > m .diskSize || int64 (len (dst )) > m .diskSize - off {
730+ return fmt .Errorf ("disk read out of range" )
731+ }
732+ if len (dst ) == 0 {
733+ return nil
734+ }
692735 _ , err := m .disk .ReadAt (dst , m .diskOff + off )
693736 if err != nil {
694737 if err == io .EOF {
@@ -699,6 +742,16 @@ func (m *Mem) ReadDisk(dst []byte, off int64) error {
699742 return nil
700743}
701744
745+ // DiskSize returns the logical size of the disk-only data.
746+ // This is the maximum offset+length across all WriteDisk calls.
747+ // On recovery after a crash, DiskSize will be the logical disk size
748+ // as of the latest recovered transaction, even if additional disk
749+ // writes to larger offsets happened after that transaction and
750+ // are still present in the file.
751+ func (m * Mem ) DiskSize () int64 {
752+ return m .diskSize
753+ }
754+
702755// mutate logs a write to the patch block, starting a new patch block if necessary.
703756// It calls commit to apply the actual write once it has checked a few
704757// error conditions.
@@ -721,7 +774,7 @@ func (m *Mem) mutate(off uint64, src []byte, commit func() error) error {
721774 p := m .ptmp [:0 ]
722775 p = binary .AppendUvarint (p , off )
723776 p = binary .AppendUvarint (p , uint64 (len (src )))
724- if len (m .patch )+ len (p )+ len (src )+ maxVarint + 1 > maxPatch {
777+ if len (m .patch )+ len (p )+ len (src )+ 2 * ( maxVarint + 1 ) > maxPatch {
725778 if err := m .flushPatch (true ); err != nil {
726779 return err
727780 }
@@ -765,6 +818,9 @@ func (m *Mem) flushPatch(needSpace bool) error {
765818 if err := m .addMemLenPatch (); err != nil {
766819 return err
767820 }
821+ if err := m .addDiskLenPatch (); err != nil {
822+ return err
823+ }
768824 p = m .patch
769825 }
770826 if len (p ) == 0 {
@@ -802,6 +858,10 @@ func (m *Mem) EndGroup() error {
802858 m .mutate (uint64 (len (m .mem ))<< 1 , nil , nil )
803859 m .patched = len (m .mem )
804860 }
861+ if m .diskPatched != m .diskSize {
862+ m .mutate (uint64 (m .diskSize )<< 1 | 1 , nil , nil )
863+ m .diskPatched = m .diskSize
864+ }
805865 m .group = - 1
806866
807867 if m .next .seq > 0 && m .compact .off == m .compact .end && len (m .patch ) > 0 {
@@ -826,6 +886,22 @@ func (m *Mem) addMemLenPatch() error {
826886 return nil
827887}
828888
889+ // addDiskLenPatch adds a final "disk length" patch to m.patch.
890+ // This records the current disk data size so that on replay,
891+ // DiskSize is correctly initialized.
892+ func (m * Mem ) addDiskLenPatch () error {
893+ if m .disk == nil || m .diskPatched == m .diskSize {
894+ return nil
895+ }
896+ if len (m .patch )+ maxVarint + 1 > maxPatch {
897+ return m .broken (fmt .Errorf ("pmem internal patch overflow" ))
898+ }
899+ m .patch = binary .AppendUvarint (m .patch , uint64 (m .diskSize )<< 1 | 1 )
900+ m .patch = binary .AppendUvarint (m .patch , 0 )
901+ m .diskPatched = m .diskSize
902+ return nil
903+ }
904+
829905// writeFrame writes a frame containing data to w.
830906func (m * Mem ) writeFrame (w * writer , data []byte ) error {
831907 f := m .tmp [:frameSize ]
@@ -857,7 +933,7 @@ func (m *Mem) writeFrame(w *writer, data []byte) error {
857933func (m * Mem ) maybeCompact (n int ) error {
858934 if m .next .seq == 0 && m .current .off < 2 * int64 (len (m .mem )) {
859935 // Current disk file is less than twice the tree memory.
860- // Not worth compacting yem .
936+ // Not worth compacting yet .
861937 return nil
862938 }
863939
@@ -935,6 +1011,15 @@ func (m *Mem) maybeCompact(n int) error {
9351011 if err := m .disk .Sync (); err != nil {
9361012 return m .broken (err )
9371013 }
1014+ // Write a disk-length patch to m.next so that on recovery
1015+ // DiskSize is correctly initialized from the compacted file.
1016+ var diskLenPatch []byte
1017+ diskLenPatch = binary .AppendUvarint (diskLenPatch , uint64 (m .diskSize )<< 1 | 1 )
1018+ diskLenPatch = binary .AppendUvarint (diskLenPatch , 0 )
1019+ if err := m .writeFrame (m .next , diskLenPatch ); err != nil {
1020+ return err
1021+ }
1022+ m .diskPatched = m .diskSize
9381023 }
9391024
9401025 // Open will start using the tree when the bigger sequence number hits the disk,
@@ -958,6 +1043,7 @@ func (m *Mem) maybeCompact(n int) error {
9581043 setCurrent (m .current .file , true , int (m .current .off ))
9591044 setCurrent (m .next .file , false , int (m .next .off ))
9601045 m .next .seq = 0
1046+ m .diskPatched = m .diskSize
9611047 return nil
9621048}
9631049
0 commit comments