msgcache

package
v2.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package msgcache reads and writes the per-folder index cache (yarilo.index.cache) that lets a listing answer without opening message files. Shared by the protocol servers: one cache, one set of invalidation rules. Format and invalidation: https://doc.yarilomail.org/latest/architecture/backend_api.html

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnvelopeTextOf

func EnvelopeTextOf(h textproto.Header) string

EnvelopeTextOf builds the envelope text for a message's header.

Types

type Handle

type Handle struct {
	// contains filtered or unexported fields
}

Handle is one request's view of a folder's cache, closed after the batched stamp. nil means "no cache": every method tolerates it (#1176).

func Open

func Open(idx mailbox.UserIndex, folderID uint64, opts Options) *Handle

Open returns a handle on the folder's cache, or nil when none can be served -- a wrong pair, an unopenable file, a backend without a cache. The caller parses as it would without one.

func (*Handle) BodyStructure

func (fc *Handle) BodyStructure(m *mailbox.MessageMeta) imaplib.BodyStructure

bodyStructure returns the cached body structure, or nil on any miss.

func (*Handle) Close

func (fc *Handle) Close()

close flushes the batched offset stamps -- one index write per FETCH, not per message -- releases the descriptor (no long-lived handles, #1176), and only then drops the locks: the stamp is part of the guarded window.

func (*Handle) Envelope

func (fc *Handle) Envelope(m *mailbox.MessageMeta) *imaplib.Envelope

envelope returns the cached envelope for a message, or nil on any of the three misses.

func (*Handle) EnvelopeAndReferences

func (fc *Handle) EnvelopeAndReferences(m *mailbox.MessageMeta) (*imaplib.Envelope, []string, bool)

EnvelopeAndReferences reads both in ONE pass over the message's record.

Asking for them separately costs two full reads -- each walks the record chain, does its own ReadAt and decodes every field in it -- and threading needs both for every message it touches. On ten thousand messages that second pass was most of what a THREAD cost: the algorithm itself is ~8ms there, while the doubled read is ~170ms (#1461).

func (*Handle) EnvelopeText

func (fc *Handle) EnvelopeText(m *mailbox.MessageMeta) (string, bool)

EnvelopeText is the stored envelope; a record holding only headers has one built from them and written back (as the reference does).

func (*Handle) GUID

func (fc *Handle) GUID(m *mailbox.MessageMeta) (string, bool)

GUID returns the cached message identifier, as the reference stores it: the hex string a client is shown.

func (*Handle) Head

func (fc *Handle) Head(m *mailbox.MessageMeta) (Head, bool)

Head returns the ordering fields of the cached envelope, or false on any of the three misses.

func (*Handle) HeadAndReferences

func (fc *Handle) HeadAndReferences(m *mailbox.MessageMeta) (Head, []string, bool)

HeadAndReferences reads both in ONE pass over the message's record, for the same reason EnvelopeAndReferences does: asking separately walks the record chain twice, which was most of what a THREAD cost (#1461).

The bool is "both halves are here", not "the head decoded". Threading needs the References as much as the envelope -- a head without them would thread the message by subject alone and put it in the wrong conversation -- so a caller that can only use the pair should not have to check two flags and remember which combination is safe.

func (*Handle) POP3UIDL

func (fc *Handle) POP3UIDL(m *mailbox.MessageMeta) (string, bool)

POP3UIDL returns the cached POP3 identifier for a message.

func (*Handle) Preload

func (fc *Handle) Preload()

Preload tells the handle it is about to be read in full, so it reads the cache file once instead of paying a syscall per record. Callers that touch one message should not call it.

func (*Handle) ReceivedDate

func (fc *Handle) ReceivedDate(m *mailbox.MessageMeta) (time.Time, bool)

ReceivedDate returns the cached internal date.

func (*Handle) References

func (fc *Handle) References(m *mailbox.MessageMeta) ([]string, bool)

References returns the cached References header as a list of message ids, or nil when the message has no record, no such field, or genuinely had no References header.

The last two cases are told apart by the empty marker: a message with no References is stored as one empty entry, so that a header nobody has to read again is not re-read for ever.

func (*Handle) SentDate

func (fc *Handle) SentDate(m *mailbox.MessageMeta) (time.Time, bool)

SentDate returns the cached Date header with the offset it carried.

func (*Handle) Sizes

func (fc *Handle) Sizes(m *mailbox.MessageMeta) (size, vsize uint32, ok bool)

Sizes returns the physical and virtual sizes the cache holds. The reference reads them here first too, before it opens anything.

func (*Handle) StoreBodyStructure

func (fc *Handle) StoreBodyStructure(m *mailbox.MessageMeta, bs imaplib.BodyStructure)

storeBodyStructure appends the freshly-parsed body structure. A structure that does not survive its own codec is not stored: serving a value the reader would reject is worse than a miss, and this is where an unknown node kind is caught.

func (*Handle) StoreEnvelopeText

func (fc *Handle) StoreEnvelopeText(m *mailbox.MessageMeta, text string)

StoreEnvelopeText caches the envelope exactly as it will be answered, built from the raw header by the reference's rules (#1714).

func (*Handle) StoreFromHeader

func (fc *Handle) StoreFromHeader(m *mailbox.MessageMeta, h textproto.Header, text string)

StoreFromHeader caches what one parse yields: the envelope, its ten headers and the record's own fields -- their cache after one FETCH ENVELOPE (#1714).

func (*Handle) StorePOP3UIDL

func (fc *Handle) StorePOP3UIDL(m *mailbox.MessageMeta, uidl string, order uint32)

StorePOP3UIDL caches the POP3 identifier and the order it was listed in, which is where the reference keeps both.

func (*Handle) StoreRecordFields

func (fc *Handle) StoreRecordFields(m *mailbox.MessageMeta)

StoreRecordFields caches what the record already knows -- sizes, dates, identifier -- while the message is open anyway for its envelope.

func (*Handle) StoreReferences

func (fc *Handle) StoreReferences(m *mailbox.MessageMeta, refs []string)

StoreReferences caches the References of a message. An empty list is stored as an empty value rather than skipped: "no References" is an answer, and skipping it would make every such message a permanent miss.

func (*Handle) StoreSentDate

func (fc *Handle) StoreSentDate(m *mailbox.MessageMeta, t time.Time)

StoreSentDate caches the Date header as parsed, with its offset.

func (*Handle) StoreSizes

func (fc *Handle) StoreSizes(m *mailbox.MessageMeta, size, vsize uint32)

StoreSizes caches both sizes. A zero is not stored: it is the value that means "not measured", and caching it would make the miss permanent.

type Head = mailbox.EnvelopeHead

Head is the part of an envelope that ordering uses: SORT and THREAD compare the sent date, the base subject, and the FIRST MAILBOX of an address list -- never a name, never a host, never a second address, and never Sender, Reply-To or Bcc at all.

It exists because decoding the whole envelope for those five strings was 30.6% of every object a SORT (DATE) allocated on a ten-thousand-message account: six address lists nobody in that command would read (#1490). The measurement is on the issue; the number that justified this is a field one.

func HeadOf

func HeadOf(env *imaplib.Envelope) Head

HeadOf takes the ordering fields off a freshly-parsed envelope, for the miss path that had to open the message.

It exists so "which fields ordering uses" is written once. Spelling it again at the call site would let the two answers drift, and the drift would show as a mailbox that sorts differently depending on whether its cache was warm.

type Index

type Index interface {
	CachePairIdentity(folderID uint64) (indexID, resetID uint32, ok bool, err error)
	// EnsureCacheExtension adds the extension to an index written before it
	// existed. Without it the lazy add is unreachable: only a stamping write
	// adds the extension, and stamping needs a pair that the missing
	// extension prevents opening.
	EnsureCacheExtension(folderID uint64) (indexID, resetID uint32, err error)
	// BumpCacheGeneration abandons the current generation and returns the
	// next file_seq. Discarding a file without it leaves the index's stamps
	// applying to whatever gets written at those offsets next.
	BumpCacheGeneration(folderID uint64) (uint32, error)
	CachePath(folderID uint64) (string, error)
	SetCacheOffsets(folderID uint64, stamps map[uint32]mailbox.CacheStamp) error
}

Index is the slice of the index surface the cache needs. Asserted at use: a backend without it serves no cache.

type Options

type Options struct {
	Locker locks.Locker
	User   string
	// SessionID names the session in the lock owner. Required: a cache write
	// holds the mailbox key, and an anonymous holder is unattributable (#1670).
	SessionID string
	Folder    string
	TraceID   string
	// DeferWrites releases the cache locks as soon as the file has been read
	// into memory, and takes them again in Close to append what the request
	// parsed.
	//
	// For a caller that writes its response while holding the handle. FETCH
	// does: it opened the pair, then read bodies from storage and wrote them
	// to a socket, all inside the locked window, so one client on a slow link
	// held both tiers -- the in-process path mutex and the cross-process
	// mailbox lock -- for as long as its transfer took. Every other session of
	// that user on that folder waited (#1545).
	DeferWrites bool

	// Shared: readers of one folder do not refuse each other. Requires
	// DeferWrites, and never for a caller that changes flags (#1673).
	Shared bool
}

type RawHeader

type RawHeader struct {
	H textproto.Header
}

RawHeader gives imaptext the header values as the message carried them: the reference caches the header's own bytes (#1714).

func (RawHeader) RawValues

func (r RawHeader) RawValues(name string) []string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL