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  }