SQLite

Check-in [d0997b0f5b]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Revise and vastly simplify the Win32 SHM file locking semantics, allowing all new tests to pass.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | readonly-wal-recovery
Files: files | file ages | folders
SHA3-256: d0997b0f5bc9a9869684e39a17a01c430d6383c8b31d6c00ea17a5eac15bc6f0
User & Date: mistachkin 2017-11-09 22:23:50.758
Context
2017-11-09
22:25
Cleanup superfluous whitespace changes. (check-in: a2908e2c88 user: mistachkin tags: readonly-wal-recovery)
22:23
Revise and vastly simplify the Win32 SHM file locking semantics, allowing all new tests to pass. (check-in: d0997b0f5b user: mistachkin tags: readonly-wal-recovery)
20:37
Add an assert() in the Win32 VFS. (check-in: 22e5833046 user: mistachkin tags: readonly-wal-recovery)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/os_win.c.
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
#endif
#ifndef SQLITE_WIN32_IOERR_RETRY_DELAY
# define SQLITE_WIN32_IOERR_RETRY_DELAY 25
#endif
static int winIoerrRetry = SQLITE_WIN32_IOERR_RETRY;
static int winIoerrRetryDelay = SQLITE_WIN32_IOERR_RETRY_DELAY;

/*
** The "winIsLockConflict" macro is used to determine if a particular I/O
** error code is due to a file locking conflict.  It must accept the error
** code DWORD as its only argument.
*/
#if !defined(winIsLockConflict)
#define winIsLockConflict(a) (((a)==NO_ERROR)                   || \
                              ((a)==ERROR_LOCK_VIOLATION)       || \
                              ((a)==ERROR_IO_PENDING))
#endif

/*
** The "winIsLockMissing" macro is used to determine if a particular I/O
** error code is due to being unable to obtain a file lock because all or
** part of the range requested within the file is missing.  It must accept
** the error code DWORD as its only argument.
*/
#if !defined(winIsLockMissing)
#define winIsLockMissing(a) (((a)==ERROR_HANDLE_EOF))
#endif

/*
** The "winIoerrCanRetry1" macro is used to determine if a particular I/O
** error code obtained via GetLastError() is eligible to be retried.  It
** must accept the error code DWORD as its only argument and should return
** non-zero if the error code is transient in nature and the operation
** responsible for generating the original error might succeed upon being
** retried.  The argument to this macro should be a variable.







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







2094
2095
2096
2097
2098
2099
2100





















2101
2102
2103
2104
2105
2106
2107
#endif
#ifndef SQLITE_WIN32_IOERR_RETRY_DELAY
# define SQLITE_WIN32_IOERR_RETRY_DELAY 25
#endif
static int winIoerrRetry = SQLITE_WIN32_IOERR_RETRY;
static int winIoerrRetryDelay = SQLITE_WIN32_IOERR_RETRY_DELAY;






















/*
** The "winIoerrCanRetry1" macro is used to determine if a particular I/O
** error code obtained via GetLastError() is eligible to be retried.  It
** must accept the error code DWORD as its only argument and should return
** non-zero if the error code is transient in nature and the operation
** responsible for generating the original error might succeed upon being
** retried.  The argument to this macro should be a variable.
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942

3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
      sqlite3_free(p);
    }else{
      pp = &p->pNext;
    }
  }
}

/*
** Query the status of the DMS lock for the specified file.  Returns
** SQLITE_OK upon success.  Upon success, the integer pointed to by
** the pLockType argument will be set to the lock type held by the
** other process, as follows:
**
**       WINSHM_UNLCK -- No locks are held on the DMS.
**       WINSHM_RDLCK -- A SHARED lock is held on the DMS.
**       WINSHM_WRLCK -- An EXCLUSIVE lock is held on the DMS.
*/
static int winGetShmDmsLockType(
  winFile *pFile, /* File handle object */
  int bReadOnly,  /* Non-zero if the SHM was opened read-only */
  int *pLockType  /* WINSHM_UNLCK, WINSHM_RDLCK, or WINSHM_WRLCK */
){
#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_OVERLAPPED)
  OVERLAPPED overlapped; /* The offset for ReadFile/WriteFile. */
#endif
  LPVOID pOverlapped = 0;
  sqlite3_int64 offset = WIN_SHM_DMS;
  BYTE notUsed1 = 0;
  DWORD notUsed2 = 0;

#if SQLITE_OS_WINCE || defined(SQLITE_WIN32_NO_OVERLAPPED)
  if( winSeekFile(pFile, offset) ){
    return SQLITE_IOERR_SEEK;
  }
#else
  memset(&overlapped, 0, sizeof(OVERLAPPED));
  overlapped.Offset = (LONG)(offset & 0xffffffff);
  overlapped.OffsetHigh = (LONG)((offset>>32) & 0x7fffffff);
  pOverlapped = &overlapped;
#endif
  if( bReadOnly ||
      !osWriteFile(pFile->h, &notUsed1, 1, &notUsed2, pOverlapped) ){
    DWORD lastErrno = bReadOnly ? NO_ERROR : osGetLastError();
    if( !osReadFile(pFile->h, &notUsed1, 1, &notUsed2, pOverlapped) ){
      lastErrno = osGetLastError();
      if( winIsLockConflict(lastErrno) ){
        if( pLockType ) *pLockType = WINSHM_WRLCK;
      }else if( winIsLockMissing(lastErrno) ){
        assert( bReadOnly );
        if( pLockType ) *pLockType = WINSHM_UNLCK;
      }else{
        return SQLITE_IOERR_READ;
      }
    }else{
      if( winIsLockConflict(lastErrno) ){
        if( pLockType ) *pLockType = WINSHM_RDLCK;
      }else{
        return SQLITE_IOERR_WRITE;
      }
    }
  }else{
    if( pLockType ) *pLockType = WINSHM_UNLCK;
  }
  return SQLITE_OK;
}

/*
** The DMS lock has not yet been taken on shm file pShmNode. Attempt to
** take it now. Return SQLITE_OK if successful, or an SQLite error
** code otherwise.
**
** If the DMS cannot be locked because this is a readonly_shm=1
** connection and no other process already holds a lock, return
** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1.
*/
static int winLockSharedMemory(winShmNode *pShmNode){
  int lockType;
  int rc = SQLITE_OK;

  /* Use ReadFile/WriteFile to determine the locks other processes are
  ** holding on the DMS byte. If it indicates that another process is
  ** holding a SHARED lock, then this process may also take a SHARED
  ** lock and proceed with opening the *-shm file.
  **
  ** Or, if no other process is holding any lock, then this process
  ** is the first to open it. In this case take an EXCLUSIVE lock on the
  ** DMS byte and truncate the *-shm file to zero bytes in size. Then
  ** downgrade to a SHARED lock on the DMS byte.
  **
  ** If another process is holding an EXCLUSIVE lock on the DMS byte,
  ** return SQLITE_BUSY to the caller (it will try again). An earlier
  ** version of this code attempted the SHARED lock at this point. But
  ** this introduced a subtle race condition: if the process holding
  ** EXCLUSIVE failed just before truncating the *-shm file, then this
  ** process might open and use the *-shm file without truncating it.
  ** And if the *-shm file has been corrupted by a power failure or
  ** system crash, the database itself may also become corrupt.  */
  if( winGetShmDmsLockType(&pShmNode->hFile, pShmNode->isReadonly,
                           &lockType)!=SQLITE_OK ){
    rc = SQLITE_IOERR_LOCK;
  }else if( lockType==WINSHM_UNLCK ){
    if( pShmNode->isReadonly ){
      pShmNode->isUnlocked = 1;

      rc = SQLITE_READONLY_CANTINIT;
    }else{
      winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
      rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1);
      if( rc==SQLITE_OK && winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){
        rc = winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(),
                         "winLockSharedMemory", pShmNode->zFilename);
      }
    }
  }else if( lockType==WINSHM_WRLCK ){
    rc = SQLITE_BUSY;
  }

  if( rc==SQLITE_OK ){
    assert( lockType==WINSHM_UNLCK || lockType==WINSHM_RDLCK );
    winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
    rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1);
  }

  return rc;
}

/*
** Open the shared-memory area associated with database file pDbFd.
**
** When opening a new shared-memory file, if no other instances of that
** file are currently open, in this process or in other processes, then








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<









<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<


>
|
|

<
<
|

|
|
<
<
|
<
<
<
|
|
<
<
<







3819
3820
3821
3822
3823
3824
3825
3826



























































3827
3828
3829
3830
3831
3832
3833
3834
3835


3836



















3837


3838
3839
3840
3841
3842
3843


3844
3845
3846
3847


3848



3849
3850



3851
3852
3853
3854
3855
3856
3857
      sqlite3_free(p);
    }else{
      pp = &p->pNext;
    }
  }
}

/*



























































** The DMS lock has not yet been taken on shm file pShmNode. Attempt to
** take it now. Return SQLITE_OK if successful, or an SQLite error
** code otherwise.
**
** If the DMS cannot be locked because this is a readonly_shm=1
** connection and no other process already holds a lock, return
** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1.
*/
static int winLockSharedMemory(winShmNode *pShmNode){


  int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1);



















  if( rc==SQLITE_OK ){


    if( pShmNode->isReadonly ){
      pShmNode->isUnlocked = 1;
      winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
      return SQLITE_READONLY_CANTINIT;
    }else if( winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){
      winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);


      return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(),
                         "winLockSharedMemory", pShmNode->zFilename);
    }
  }






  winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1);
  return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1);



}

/*
** Open the shared-memory area associated with database file pDbFd.
**
** When opening a new shared-memory file, if no other instances of that
** file are currently open, in this process or in other processes, then