github.com/ncruces/go-sqlite3@v0.15.1-0.20240520133447-53eef1510ff0/vfs/os_windows.go (about)

     1  //go:build !sqlite3_nosys
     2  
     3  package vfs
     4  
     5  import (
     6  	"math/rand"
     7  	"os"
     8  	"time"
     9  
    10  	"golang.org/x/sys/windows"
    11  )
    12  
    13  func osGetSharedLock(file *os.File) _ErrorCode {
    14  	// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
    15  	rc := osReadLock(file, _PENDING_BYTE, 1, 0)
    16  	if rc == _OK {
    17  		// Acquire the SHARED lock.
    18  		rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
    19  
    20  		// Release the PENDING lock.
    21  		osUnlock(file, _PENDING_BYTE, 1)
    22  	}
    23  	return rc
    24  }
    25  
    26  func osGetReservedLock(file *os.File) _ErrorCode {
    27  	// Acquire the RESERVED lock.
    28  	return osWriteLock(file, _RESERVED_BYTE, 1, 0)
    29  }
    30  
    31  func osGetPendingLock(file *os.File, block bool) _ErrorCode {
    32  	var timeout time.Duration
    33  	if block {
    34  		timeout = -1
    35  	}
    36  
    37  	// Acquire the PENDING lock.
    38  	return osWriteLock(file, _PENDING_BYTE, 1, timeout)
    39  }
    40  
    41  func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
    42  	var timeout time.Duration
    43  	if wait {
    44  		timeout = time.Millisecond
    45  	}
    46  
    47  	// Release the SHARED lock.
    48  	osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
    49  
    50  	// Acquire the EXCLUSIVE lock.
    51  	rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
    52  
    53  	if rc != _OK {
    54  		// Reacquire the SHARED lock.
    55  		osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
    56  	}
    57  	return rc
    58  }
    59  
    60  func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
    61  	if state >= LOCK_EXCLUSIVE {
    62  		// Release the EXCLUSIVE lock.
    63  		osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
    64  
    65  		// Reacquire the SHARED lock.
    66  		if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
    67  			// This should never happen.
    68  			// We should always be able to reacquire the read lock.
    69  			return _IOERR_RDLOCK
    70  		}
    71  	}
    72  
    73  	// Release the PENDING and RESERVED locks.
    74  	if state >= LOCK_RESERVED {
    75  		osUnlock(file, _RESERVED_BYTE, 1)
    76  	}
    77  	if state >= LOCK_PENDING {
    78  		osUnlock(file, _PENDING_BYTE, 1)
    79  	}
    80  	return _OK
    81  }
    82  
    83  func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
    84  	// Release all locks.
    85  	if state >= LOCK_RESERVED {
    86  		osUnlock(file, _RESERVED_BYTE, 1)
    87  	}
    88  	if state >= LOCK_SHARED {
    89  		osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
    90  	}
    91  	if state >= LOCK_PENDING {
    92  		osUnlock(file, _PENDING_BYTE, 1)
    93  	}
    94  	return _OK
    95  }
    96  
    97  func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
    98  	// Test the RESERVED lock.
    99  	rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK)
   100  	if rc == _BUSY {
   101  		return true, _OK
   102  	}
   103  	if rc == _OK {
   104  		// Release the RESERVED lock.
   105  		osUnlock(file, _RESERVED_BYTE, 1)
   106  	}
   107  	return false, rc
   108  }
   109  
   110  func osUnlock(file *os.File, start, len uint32) _ErrorCode {
   111  	err := windows.UnlockFileEx(windows.Handle(file.Fd()),
   112  		0, len, 0, &windows.Overlapped{Offset: start})
   113  	if err == windows.ERROR_NOT_LOCKED {
   114  		return _OK
   115  	}
   116  	if err != nil {
   117  		return _IOERR_UNLOCK
   118  	}
   119  	return _OK
   120  }
   121  
   122  func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
   123  	var err error
   124  	switch {
   125  	case timeout == 0:
   126  		err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
   127  	case timeout < 0:
   128  		err = osLockEx(file, flags, start, len)
   129  	default:
   130  		before := time.Now()
   131  		for {
   132  			err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
   133  			if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
   134  				break
   135  			}
   136  			if timeout < time.Since(before) {
   137  				break
   138  			}
   139  			osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
   140  		}
   141  	}
   142  	return osLockErrorCode(err, def)
   143  }
   144  
   145  func osLockEx(file *os.File, flags, start, len uint32) error {
   146  	return windows.LockFileEx(windows.Handle(file.Fd()), flags,
   147  		0, len, 0, &windows.Overlapped{Offset: start})
   148  }
   149  
   150  func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
   151  	return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
   152  }
   153  
   154  func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
   155  	return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
   156  }
   157  
   158  func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
   159  	if err == nil {
   160  		return _OK
   161  	}
   162  	if errno, ok := err.(windows.Errno); ok {
   163  		// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
   164  		switch errno {
   165  		case
   166  			windows.ERROR_LOCK_VIOLATION,
   167  			windows.ERROR_IO_PENDING,
   168  			windows.ERROR_OPERATION_ABORTED:
   169  			return _BUSY
   170  		}
   171  	}
   172  	return def
   173  }
   174  
   175  func osSleep(d time.Duration) {
   176  	if d > 0 {
   177  		period := max(1, d/(5*time.Millisecond))
   178  		if period < 16 {
   179  			windows.TimeBeginPeriod(uint32(period))
   180  		}
   181  		time.Sleep(d)
   182  		if period < 16 {
   183  			windows.TimeEndPeriod(uint32(period))
   184  		}
   185  	}
   186  }