github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/vfs/shm.go (about) 1 //go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys) 2 3 package vfs 4 5 import ( 6 "context" 7 "io" 8 "os" 9 10 "github.com/ncruces/go-sqlite3/internal/util" 11 "github.com/tetratelabs/wazero/api" 12 "golang.org/x/sys/unix" 13 ) 14 15 // SupportsSharedMemory is false on platforms that do not support shared memory. 16 // To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode]. 17 // 18 // [WAL without shared-memory]: https://sqlite.org/wal.html#noshm 19 // [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode 20 const SupportsSharedMemory = true 21 22 const ( 23 _SHM_NLOCK = 8 24 _SHM_BASE = 120 25 _SHM_DMS = _SHM_BASE + _SHM_NLOCK 26 ) 27 28 func (f *vfsFile) SharedMemory() SharedMemory { return f.shm } 29 30 // NewSharedMemory returns a shared-memory WAL-index 31 // backed by a file with the given path. 32 // It will return nil if shared-memory is not supported, 33 // or not appropriate for the given flags. 34 // Only [OPEN_MAIN_DB] databases may need a WAL-index. 35 // You must ensure all concurrent accesses to a database 36 // use shared-memory instances created with the same path. 37 func NewSharedMemory(path string, flags OpenFlag) SharedMemory { 38 if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 { 39 return nil 40 } 41 return &vfsShm{ 42 path: path, 43 readOnly: flags&OPEN_READONLY != 0, 44 } 45 } 46 47 type vfsShm struct { 48 *os.File 49 path string 50 regions []*util.MappedRegion 51 readOnly bool 52 } 53 54 func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) { 55 // Ensure size is a multiple of the OS page size. 56 if int(size)&(unix.Getpagesize()-1) != 0 { 57 return 0, _IOERR_SHMMAP 58 } 59 60 if s.File == nil { 61 var flag int 62 if s.readOnly { 63 flag = unix.O_RDONLY 64 } else { 65 flag = unix.O_RDWR 66 } 67 f, err := os.OpenFile(s.path, 68 flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666) 69 if err != nil { 70 return 0, _CANTOPEN 71 } 72 s.File = f 73 } 74 75 // Dead man's switch. 76 if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK { 77 return 0, _IOERR_LOCK 78 } else if lock == unix.F_WRLCK { 79 return 0, _BUSY 80 } else if lock == unix.F_UNLCK { 81 if s.readOnly { 82 return 0, _READONLY_CANTINIT 83 } 84 if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK { 85 return 0, rc 86 } 87 if err := s.Truncate(0); err != nil { 88 return 0, _IOERR_SHMOPEN 89 } 90 } 91 if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK { 92 return 0, rc 93 } 94 95 // Check if file is big enough. 96 o, err := s.Seek(0, io.SeekEnd) 97 if err != nil { 98 return 0, _IOERR_SHMSIZE 99 } 100 if n := (int64(id) + 1) * int64(size); n > o { 101 if !extend { 102 return 0, nil 103 } 104 err := osAllocate(s.File, n) 105 if err != nil { 106 return 0, _IOERR_SHMSIZE 107 } 108 } 109 110 var prot int 111 if s.readOnly { 112 prot = unix.PROT_READ 113 } else { 114 prot = unix.PROT_READ | unix.PROT_WRITE 115 } 116 r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot) 117 if err != nil { 118 return 0, err 119 } 120 s.regions = append(s.regions, r) 121 return r.Ptr, nil 122 } 123 124 func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { 125 // Argument check. 126 if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK { 127 panic(util.AssertErr()) 128 } 129 switch flags { 130 case 131 _SHM_LOCK | _SHM_SHARED, 132 _SHM_LOCK | _SHM_EXCLUSIVE, 133 _SHM_UNLOCK | _SHM_SHARED, 134 _SHM_UNLOCK | _SHM_EXCLUSIVE: 135 // 136 default: 137 panic(util.AssertErr()) 138 } 139 if n != 1 && flags&_SHM_EXCLUSIVE == 0 { 140 panic(util.AssertErr()) 141 } 142 143 switch { 144 case flags&_SHM_UNLOCK != 0: 145 return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n)) 146 case flags&_SHM_SHARED != 0: 147 return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) 148 case flags&_SHM_EXCLUSIVE != 0: 149 return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0) 150 default: 151 panic(util.AssertErr()) 152 } 153 } 154 155 func (s *vfsShm) shmUnmap(delete bool) { 156 if s.File == nil { 157 return 158 } 159 160 // Unmap regions. 161 for _, r := range s.regions { 162 r.Unmap() 163 } 164 clear(s.regions) 165 s.regions = s.regions[:0] 166 167 // Close the file. 168 defer s.Close() 169 if delete { 170 os.Remove(s.Name()) 171 } 172 s.File = nil 173 }