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
@@ -401,6 +414,7 @@ func open(magic string, file1, file2, disk File) (_ *Mem, err error) {
401414 if err := m .readFile (r1 ); err != nil {
402415 return nil , err
403416 }
417+ m .diskPatched = m .diskSize
404418 m .current = newWriter (r1 .file , r1 .seq )
405419 m .current .off = r1 .off
406420 m .next = newWriter (r2 .file , 0 )
@@ -552,9 +566,12 @@ func (m *Mem) replay(patch []byte) error {
552566 }
553567 if isDisk {
554568 // disk patch
555- if _ , err := m .disk .WriteAt (patch [:count ], m .diskOff + int64 (off )); err != nil {
556- return m .broken (err )
569+ if count > 0 {
570+ if _ , err := m .disk .WriteAt (patch [:count ], m .diskOff + int64 (off )); err != nil {
571+ return m .broken (err )
572+ }
557573 }
574+ m .diskSize = max (m .diskSize , int64 (off + count ))
558575 } else {
559576 // memory patch
560577 if off + count > uint64 (len (m .mem )) {
@@ -607,16 +624,30 @@ func (m *Mem) Offset(b []byte) (offset int, ok bool) {
607624 return int (off ), ok
608625}
609626
610- // BeginGroup starts an atomic mutation group.
611- // Expand and Mutate calls between Begin and [Mem.EndGroup]
612- // are guaranteed to be observed as an atomic unit
613- // upon reloading the memory: either they will all be
614- // present or none of them will be.
627+ // BeginGroup starts an atomic mutation group (a transaction).
628+ //
615629// Calls to BeginGroup must be followed eventually by a call to EndGroup
616630// and cannot be nested: it is an error to call BeginGroup twice
617631// without an intervening EndGroup.
618632//
619- // A group is limited to mutation of at most MaxGroupBytes bytes of mutated data.
633+ // For the Expand, Mutate, and WriteDisk calls between Begin and [Mem.EndGroup],
634+ // there are three possible outcomes after a Mem has been reloaded:
635+ //
636+ // 1. All of the effects will be observed (group reloaded).
637+ // 2. None of the effects will be observed (group not reloaded).
638+ // 3. No memory effects will be observed, and the WriteDisk calls
639+ // will not be observable by [Mem.DiskSize], but some or all of the
640+ // disk writes could still be observed by [Mem.ReadDisk].
641+ //
642+ // Considering only the memory, the group is an atomic unit,
643+ // either fully observed or not observed at all.
644+ // The disk is added to the group in a one-sided manner:
645+ // if the memory changes are observed, then all the disk changes are observed too,
646+ // but not the reverse: later disk changes (all of them or a subset of them) can be observed
647+ // without their corresponding memory changes.
648+ //
649+ // A group is limited to mutation of at most MaxGroupBytes bytes of data
650+ // modified by the combination of Mutate and WriteDisk.
620651func (m * Mem ) BeginGroup () error {
621652 if m .err != nil {
622653 return m .err
@@ -625,13 +656,17 @@ func (m *Mem) BeginGroup() error {
625656 return fmt .Errorf ("atomic mutation group already begun" )
626657 }
627658
628- // Patch buffer always has room to add an empty mutation
629- // at the end of the memory, to represent the most recent Expand.
630- // If the group grows too large, we will flush up to but not
631- // including the group, so add the empty mutation now.
659+ // Patch buffer always has room to add empty mutations
660+ // at the end of the memory and disk, to represent the most
661+ // recent Expand/WriteDisk. If the group grows too large,
662+ // we will flush up to but not including the group,
663+ // so add the empty mutations now.
632664 if err := m .addMemLenPatch (); err != nil {
633665 return err
634666 }
667+ if err := m .addDiskLenPatch (); err != nil {
668+ return err
669+ }
635670
636671 m .group = len (m .patch )
637672 m .groupData = 0
@@ -665,9 +700,9 @@ func (m *Mem) Mutate(dst, src []byte) error {
665700
666701// WriteDisk writes src to the disk-only file at offset off.
667702// It guarantees that on recovery after a crash,
668- // all disk writes that occurred before the latest recovered Mutate
669- // will be available for reading.
670- // (Disk writes that happened after that Mutate may or may not
703+ // all disk writes that occurred in or before
704+ // the latest recovered transaction will be available for reading.
705+ // (Disk writes that happened after that transaction may or may not
671706// be available for reading as well.)
672707func (m * Mem ) WriteDisk (src []byte , off int64 ) error {
673708 if m .err != nil {
@@ -681,6 +716,8 @@ func (m *Mem) WriteDisk(src []byte, off int64) error {
681716 if err != nil {
682717 return m .broken (err )
683718 }
719+ m .diskSize = max (m .diskSize , off + int64 (len (src )))
720+ m .diskPatched = max (m .diskPatched , off + int64 (len (src )))
684721 return nil
685722 })
686723}
@@ -690,6 +727,12 @@ func (m *Mem) ReadDisk(dst []byte, off int64) error {
690727 if m .err != nil {
691728 return m .err
692729 }
730+ if off < 0 || off > m .diskSize || int64 (len (dst )) > m .diskSize - off {
731+ return fmt .Errorf ("disk read out of range" )
732+ }
733+ if len (dst ) == 0 {
734+ return nil
735+ }
693736 _ , err := m .disk .ReadAt (dst , m .diskOff + off )
694737 if err != nil {
695738 if err == io .EOF {
@@ -700,6 +743,16 @@ func (m *Mem) ReadDisk(dst []byte, off int64) error {
700743 return nil
701744}
702745
746+ // DiskSize returns the logical size of the disk-only data.
747+ // This is the maximum offset+length across all WriteDisk calls.
748+ // On recovery after a crash, DiskSize will be the logical disk size
749+ // as of the latest recovered transaction, even if additional disk
750+ // writes to larger offsets happened after that transaction and
751+ // are still present in the file.
752+ func (m * Mem ) DiskSize () int64 {
753+ return m .diskSize
754+ }
755+
703756// mutate logs a write to the patch block, starting a new patch block if necessary.
704757// It calls commit to apply the actual write once it has checked a few
705758// error conditions.
@@ -722,7 +775,7 @@ func (m *Mem) mutate(off uint64, src []byte, commit func() error) error {
722775 p := m .ptmp [:0 ]
723776 p = binary .AppendUvarint (p , off )
724777 p = binary .AppendUvarint (p , uint64 (len (src )))
725- if len (m .patch )+ len (p )+ len (src )+ maxVarint + 1 > maxPatch {
778+ if len (m .patch )+ len (p )+ len (src )+ 2 * ( maxVarint + 1 ) > maxPatch {
726779 if err := m .flushPatch (true ); err != nil {
727780 return err
728781 }
@@ -766,6 +819,9 @@ func (m *Mem) flushPatch(needSpace bool) error {
766819 if err := m .addMemLenPatch (); err != nil {
767820 return err
768821 }
822+ if err := m .addDiskLenPatch (); err != nil {
823+ return err
824+ }
769825 p = m .patch
770826 }
771827 if len (p ) == 0 {
@@ -803,6 +859,10 @@ func (m *Mem) EndGroup() error {
803859 m .mutate (uint64 (len (m .mem ))<< 1 , nil , nil )
804860 m .patched = len (m .mem )
805861 }
862+ if m .diskPatched != m .diskSize {
863+ m .mutate (uint64 (m .diskSize )<< 1 | 1 , nil , nil )
864+ m .diskPatched = m .diskSize
865+ }
806866 m .group = - 1
807867
808868 if m .next .seq > 0 && m .compact .off == m .compact .end && len (m .patch ) > 0 {
@@ -827,6 +887,22 @@ func (m *Mem) addMemLenPatch() error {
827887 return nil
828888}
829889
890+ // addDiskLenPatch adds a final "disk length" patch to m.patch.
891+ // This records the current disk data size so that on replay,
892+ // DiskSize is correctly initialized.
893+ func (m * Mem ) addDiskLenPatch () error {
894+ if m .disk == nil || m .diskPatched == m .diskSize {
895+ return nil
896+ }
897+ if len (m .patch )+ maxVarint + 1 > maxPatch {
898+ return m .broken (fmt .Errorf ("pmem internal patch overflow" ))
899+ }
900+ m .patch = binary .AppendUvarint (m .patch , uint64 (m .diskSize )<< 1 | 1 )
901+ m .patch = binary .AppendUvarint (m .patch , 0 )
902+ m .diskPatched = m .diskSize
903+ return nil
904+ }
905+
830906// writeFrame writes a frame containing data to w.
831907func (m * Mem ) writeFrame (w * writer , data []byte ) error {
832908 f := m .tmp [:frameSize ]
@@ -858,7 +934,7 @@ func (m *Mem) writeFrame(w *writer, data []byte) error {
858934func (m * Mem ) maybeCompact (n int ) error {
859935 if m .next .seq == 0 && m .current .off < 2 * int64 (len (m .mem )) {
860936 // Current disk file is less than twice the tree memory.
861- // Not worth compacting yem .
937+ // Not worth compacting yet .
862938 return nil
863939 }
864940
@@ -936,6 +1012,15 @@ func (m *Mem) maybeCompact(n int) error {
9361012 if err := m .disk .Sync (); err != nil {
9371013 return m .broken (err )
9381014 }
1015+ // Write a disk-length patch to m.next so that on recovery
1016+ // DiskSize is correctly initialized from the compacted file.
1017+ var diskLenPatch []byte
1018+ diskLenPatch = binary .AppendUvarint (diskLenPatch , uint64 (m .diskSize )<< 1 | 1 )
1019+ diskLenPatch = binary .AppendUvarint (diskLenPatch , 0 )
1020+ if err := m .writeFrame (m .next , diskLenPatch ); err != nil {
1021+ return err
1022+ }
1023+ m .diskPatched = m .diskSize
9391024 }
9401025
9411026 // Open will start using the tree when the bigger sequence number hits the disk,
@@ -959,6 +1044,7 @@ func (m *Mem) maybeCompact(n int) error {
9591044 setCurrent (m .current .file , true , int (m .current .off ))
9601045 setCurrent (m .next .file , false , int (m .next .off ))
9611046 m .next .seq = 0
1047+ m .diskPatched = m .diskSize
9621048 return nil
9631049}
9641050
0 commit comments