shm_dotlk.go

  1//go:build sqlite3_dotlk
  2
  3package vfs
  4
  5import (
  6	"context"
  7	"errors"
  8	"io/fs"
  9	"sync"
 10
 11	"github.com/tetratelabs/wazero/api"
 12
 13	"github.com/ncruces/go-sqlite3/internal/dotlk"
 14	"github.com/ncruces/go-sqlite3/internal/util"
 15)
 16
 17type vfsShmParent struct {
 18	shared [][_WALINDEX_PGSZ]byte
 19	refs   int // +checklocks:vfsShmListMtx
 20
 21	lock [_SHM_NLOCK]int8 // +checklocks:Mutex
 22	sync.Mutex
 23}
 24
 25var (
 26	// +checklocks:vfsShmListMtx
 27	vfsShmList    = map[string]*vfsShmParent{}
 28	vfsShmListMtx sync.Mutex
 29)
 30
 31type vfsShm struct {
 32	*vfsShmParent
 33	mod    api.Module
 34	alloc  api.Function
 35	free   api.Function
 36	path   string
 37	shadow [][_WALINDEX_PGSZ]byte
 38	ptrs   []ptr_t
 39	stack  [1]stk_t
 40	lock   [_SHM_NLOCK]bool
 41}
 42
 43func (s *vfsShm) Close() error {
 44	if s.vfsShmParent == nil {
 45		return nil
 46	}
 47
 48	vfsShmListMtx.Lock()
 49	defer vfsShmListMtx.Unlock()
 50
 51	// Unlock everything.
 52	s.shmLock(0, _SHM_NLOCK, _SHM_UNLOCK)
 53
 54	// Decrease reference count.
 55	if s.vfsShmParent.refs > 0 {
 56		s.vfsShmParent.refs--
 57		s.vfsShmParent = nil
 58		return nil
 59	}
 60
 61	if err := dotlk.Unlock(s.path); err != nil {
 62		return _IOERR_UNLOCK
 63	}
 64	delete(vfsShmList, s.path)
 65	s.vfsShmParent = nil
 66	return nil
 67}
 68
 69func (s *vfsShm) shmOpen() _ErrorCode {
 70	if s.vfsShmParent != nil {
 71		return _OK
 72	}
 73
 74	vfsShmListMtx.Lock()
 75	defer vfsShmListMtx.Unlock()
 76
 77	// Find a shared buffer, increase the reference count.
 78	if g, ok := vfsShmList[s.path]; ok {
 79		s.vfsShmParent = g
 80		g.refs++
 81		return _OK
 82	}
 83
 84	// Dead man's switch.
 85	err := dotlk.LockShm(s.path)
 86	if errors.Is(err, fs.ErrExist) {
 87		return _BUSY
 88	}
 89	if err != nil {
 90		return _IOERR_LOCK
 91	}
 92
 93	// Add the new shared buffer.
 94	s.vfsShmParent = &vfsShmParent{}
 95	vfsShmList[s.path] = s.vfsShmParent
 96	return _OK
 97}
 98
 99func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) {
100	if size != _WALINDEX_PGSZ {
101		return 0, _IOERR_SHMMAP
102	}
103	if s.mod == nil {
104		s.mod = mod
105		s.free = mod.ExportedFunction("sqlite3_free")
106		s.alloc = mod.ExportedFunction("sqlite3_malloc64")
107	}
108	if rc := s.shmOpen(); rc != _OK {
109		return 0, rc
110	}
111
112	s.Lock()
113	defer s.Unlock()
114	defer s.shmAcquire(nil)
115
116	// Extend shared memory.
117	if int(id) >= len(s.shared) {
118		if !extend {
119			return 0, _OK
120		}
121		s.shared = append(s.shared, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shared)+1)...)
122	}
123
124	// Allocate shadow memory.
125	if int(id) >= len(s.shadow) {
126		s.shadow = append(s.shadow, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shadow)+1)...)
127	}
128
129	// Allocate local memory.
130	for int(id) >= len(s.ptrs) {
131		s.stack[0] = stk_t(size)
132		if err := s.alloc.CallWithStack(ctx, s.stack[:]); err != nil {
133			panic(err)
134		}
135		if s.stack[0] == 0 {
136			panic(util.OOMErr)
137		}
138		clear(util.View(s.mod, ptr_t(s.stack[0]), _WALINDEX_PGSZ))
139		s.ptrs = append(s.ptrs, ptr_t(s.stack[0]))
140	}
141
142	s.shadow[0][4] = 1
143	return s.ptrs[id], _OK
144}
145
146func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) {
147	s.Lock()
148	defer s.Unlock()
149
150	switch {
151	case flags&_SHM_LOCK != 0:
152		defer s.shmAcquire(&rc)
153	case flags&_SHM_EXCLUSIVE != 0:
154		s.shmRelease()
155	}
156
157	return s.shmMemLock(offset, n, flags)
158}
159
160func (s *vfsShm) shmUnmap(delete bool) {
161	if s.vfsShmParent == nil {
162		return
163	}
164	defer s.Close()
165
166	s.Lock()
167	s.shmRelease()
168	defer s.Unlock()
169
170	for _, p := range s.ptrs {
171		s.stack[0] = stk_t(p)
172		if err := s.free.CallWithStack(context.Background(), s.stack[:]); err != nil {
173			panic(err)
174		}
175	}
176	s.ptrs = nil
177	s.shadow = nil
178}