/ Check-in [5e9dd3bd]
Login

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

Overview
Comment:Improve the logLockRegion() function in log.c.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | wal
Files: files | file ages | folders
SHA1: 5e9dd3bd8e829376408925fb4cfcd5bb1eb1105f
User & Date: dan 2010-04-14 15:49:40
Context
2010-04-14
18:06
Add tests to check inter-process WAL locking. check-in: 9435f313 user: dan tags: wal
15:49
Improve the logLockRegion() function in log.c. check-in: 5e9dd3bd user: dan tags: wal
11:23
Fixes for locking issues in WAL mode. check-in: a9617eff user: dan tags: wal
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/log.c.

985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004


1005
1006
1007
1008
1009
1010
1011
1012
1013
1014






1015
1016
1017
1018












1019
1020
1021

1022

1023
1024
1025

1026
1027
1028







1029
1030


1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047

1048














1049
1050
1051



















1052
1053
1054
1055
1056


1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069


1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
....
1301
1302
1303
1304
1305
1306
1307







1308
1309
1310
1311
1312
1313
1314
#define LOG_UNLOCK 0
#define LOG_RDLOCK 1
#define LOG_WRLOCK 2

static int logLockRegion(Log *pLog, u32 mRegion, int op){
  LogSummary *pSummary = pLog->pSummary;
  LogLock *p;                     /* Used to iterate through in-process locks */
  u32 mNew;                       /* New locks on file */
  u32 mOld;                       /* Old locks on file */
  u32 mNewLock;                   /* New locks held by pLog */

  assert( 
       /* Writer lock operations */
          (op==LOG_WRLOCK && mRegion==(LOG_REGION_C|LOG_REGION_D))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_C|LOG_REGION_D))

       /* Reader lock operations */
       || (op==LOG_RDLOCK && mRegion==(LOG_REGION_A|LOG_REGION_B))
       || (op==LOG_RDLOCK && mRegion==(LOG_REGION_D))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_A))


       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_B))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_D))

       /* Checkpointer lock operations */
       || (op==LOG_WRLOCK && mRegion==(LOG_REGION_B|LOG_REGION_C))
       || (op==LOG_WRLOCK && mRegion==(LOG_REGION_A))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_A|LOG_REGION_B|LOG_REGION_C))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_B|LOG_REGION_C))
  );







  sqlite3_mutex_enter(pSummary->mutex);

  /* If obtaining (not releasing) a lock, check if there exist any 
  ** conflicting locks in process. Return SQLITE_BUSY in this case.












  */
  if( op ){
    u32 mConflict = (mRegion<<8) | ((op==LOG_WRLOCK) ? mRegion : 0);

    for(p=pSummary->pLock; p; p=p->pNext){

      if( p!=&pLog->lock && (p->mLock & mConflict) ){
        sqlite3_mutex_leave(pSummary->mutex);
        return SQLITE_BUSY;

      }
    }
  }








  /* Determine the new lock mask for this log connection */


  switch( op ){
    case LOG_UNLOCK: 
      mNewLock = (pLog->lock.mLock & ~(mRegion|(mRegion<<8))); 
      break;
    case LOG_RDLOCK:
      mNewLock = ((pLog->lock.mLock & ~(mRegion<<8)) | mRegion);
      break;
    default:
      assert( op==LOG_WRLOCK );
      mNewLock = (pLog->lock.mLock | (mRegion<<8) | mRegion);
      break;
  }

  /* Determine the current and desired sets of locks at the file level. */
  mNew = 0;
  for(p=pSummary->pLock; p; p=p->pNext){
    assert( (p->mLock & (p->mLock<<8))==(p->mLock & 0x00000F00) );

    if( p!=&pLog->lock ) mNew |= p->mLock;














  }
  mOld = mNew | pLog->lock.mLock;
  mNew = mNew | mNewLock;




















  if( mNew!=mOld ){
    int rc;
    u32 mChange = (mNew^mOld) | ((mNew^mOld)>>8);
    struct flock f;


    memset(&f, 0, sizeof(f));
    f.l_type = (op==LOG_WRLOCK?F_WRLCK:(op==LOG_RDLOCK?F_RDLCK:F_UNLCK));
    f.l_whence = SEEK_SET;

    if(      mChange & LOG_REGION_A ) f.l_start = 12;
    else if( mChange & LOG_REGION_B ) f.l_start = 13;
    else if( mChange & LOG_REGION_C ) f.l_start = 14;
    else if( mChange & LOG_REGION_D ) f.l_start = 15;

    if(      mChange & LOG_REGION_D ) f.l_len   = 16 - f.l_start;
    else if( mChange & LOG_REGION_C ) f.l_len   = 15 - f.l_start;
    else if( mChange & LOG_REGION_B ) f.l_len   = 14 - f.l_start;
    else if( mChange & LOG_REGION_A ) f.l_len   = 13 - f.l_start;



    rc = fcntl(pSummary->fd, F_SETLK, &f);
    if( rc!=0 ){
      sqlite3_mutex_leave(pSummary->mutex);
      return SQLITE_BUSY;
    }
  }

  pLog->lock.mLock = mNewLock;
  sqlite3_mutex_leave(pSummary->mutex);
  return SQLITE_OK;
}

/*
** Try to read the log-summary header. Attempt to verify the header
** checksum. If the checksum can be verified, copy the log-summary
................................................................................
  if( op ){

    /* Obtain the writer lock */
    int rc = logLockRegion(pLog, LOG_REGION_C|LOG_REGION_D, LOG_WRLOCK);
    if( rc!=SQLITE_OK ){
      return rc;
    }








    if( memcmp(&pLog->hdr, pLog->pSummary->aData, sizeof(pLog->hdr)) ){
      return SQLITE_BUSY;
    }
    pLog->isWriteLocked = 1;

  }else if( pLog->isWriteLocked ){







|
|
<






|

|
|
>
>
|





|
|


>
>
>
>
>
>


<
<
>
>
>
>
>
>
>
>
>
>
>
>

<
<
>
|
>
|
<
<
>
|
|
|
>
>
>
>
>
>
>
|
<
>
>


|


|



|



|
|
|
<
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

<
<
<
<
>
>



<
<
<
<
<
<
<
<
<
<
>
>








|







 







>
>
>
>
>
>
>







985
986
987
988
989
990
991
992
993

994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023


1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036


1037
1038
1039
1040


1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052

1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070

1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087


1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107




1108
1109
1110
1111
1112










1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
....
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
#define LOG_UNLOCK 0
#define LOG_RDLOCK 1
#define LOG_WRLOCK 2

static int logLockRegion(Log *pLog, u32 mRegion, int op){
  LogSummary *pSummary = pLog->pSummary;
  LogLock *p;                     /* Used to iterate through in-process locks */
  u32 mOther;                     /* Locks held by other connections */
  u32 mNew;                       /* New mask for pLog */


  assert( 
       /* Writer lock operations */
          (op==LOG_WRLOCK && mRegion==(LOG_REGION_C|LOG_REGION_D))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_C|LOG_REGION_D))

       /* Normal reader lock operations */
       || (op==LOG_RDLOCK && mRegion==(LOG_REGION_A|LOG_REGION_B))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_A))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_B))

       /* Region D reader lock operations */
       || (op==LOG_RDLOCK && mRegion==(LOG_REGION_D))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_D))

       /* Checkpointer lock operations */
       || (op==LOG_WRLOCK && mRegion==(LOG_REGION_B|LOG_REGION_C))
       || (op==LOG_WRLOCK && mRegion==(LOG_REGION_A))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_B|LOG_REGION_C))
       || (op==LOG_UNLOCK && mRegion==(LOG_REGION_A|LOG_REGION_B|LOG_REGION_C))
  );

  /* Assert that a connection never tries to go from an EXCLUSIVE to a 
  ** SHARED lock on a region. Moving from SHARED to EXCLUSIVE sometimes
  ** happens though (when a region D reader upgrades to a writer).
  */
  assert( op!=LOG_RDLOCK || 0==(pLog->lock.mLock & (mRegion<<8)) );

  sqlite3_mutex_enter(pSummary->mutex);



  /* Calculate a mask of logs held by all connections in this process apart
  ** from this one. The least significant byte of the mask contains a mask
  ** of the SHARED logs held. The next least significant byte of the mask
  ** indicates the EXCLUSIVE locks held. For example, to test if some other
  ** connection is holding a SHARED lock on region A, or an EXCLUSIVE lock
  ** on region C, do:
  **
  **   hasSharedOnA    = (mOther & (LOG_REGION_A<<0));
  **   hasExclusiveOnC = (mOther & (LOG_REGION_C<<8));
  **
  ** In all masks, if the bit in the EXCLUSIVE byte mask is set, so is the 
  ** corresponding bit in the SHARED mask.
  */


  mOther = 0;
  for(p=pSummary->pLock; p; p=p->pNext){
    assert( (p->mLock & (p->mLock<<8))==(p->mLock&0x0000FF00) );
    if( p!=&pLog->lock ){


      mOther |= p->mLock;
    }
  }

  /* If this call is to lock a region (not to unlock one), test if locks held
  ** by any other connection in this process prevent the new locks from
  ** begin granted. If so, exit the summary mutex and return SQLITE_BUSY.
  */
  if( op && (mOther & (mRegion << (op==LOG_RDLOCK ? 8 : 0))) ){
    sqlite3_mutex_leave(pSummary->mutex);
    return SQLITE_BUSY;
  }


  /* Figure out the new log mask for this connection. */
  switch( op ){
    case LOG_UNLOCK: 
      mNew = (pLog->lock.mLock & ~(mRegion|(mRegion<<8)));
      break;
    case LOG_RDLOCK:
      mNew = (pLog->lock.mLock | mRegion);
      break;
    default:
      assert( op==LOG_WRLOCK );
      mNew = (pLog->lock.mLock | (mRegion<<8) | mRegion);
      break;
  }

  /* Now modify the locks held on the log-summary file descriptor. This
  ** file descriptor is shared by all log connections in this process. 
  ** Therefore:

  **
  **   + If one or more log connections in this process hold a SHARED lock
  **     on a region, the file-descriptor should hold a SHARED lock on
  **     the file region.
  **
  **   + If a log connection in this process holds an EXCLUSIVE lock on a
  **     region, the file-descriptor should also hold an EXCLUSIVE lock on
  **     the region in question.
  **
  ** If this is an LOG_UNLOCK operation, only regions for which no other
  ** connection holds a lock should actually be unlocked. And if this
  ** is a LOG_RDLOCK operation and other connections already hold all
  ** the required SHARED locks, then no system call is required.
  */
  if( op==LOG_UNLOCK ){
    mRegion = (mRegion & ~mOther);
  }


  if( (op==LOG_WRLOCK)
   || (op==LOG_UNLOCK && mRegion) 
   || (op==LOG_RDLOCK && (mOther&mRegion)!=mRegion)
  ){
    struct LockMap {
      int iStart;                 /* Byte offset to start locking operation */
      int iLen;                   /* Length field for locking operation */
    } aMap[] = {
      /* 0000 */ {0, 0},    /* 0001 */ {4, 1}, 
      /* 0010 */ {3, 1},    /* 0011 */ {3, 2},
      /* 0100 */ {2, 1},    /* 0101 */ {0, 0}, 
      /* 0110 */ {2, 2},    /* 0111 */ {2, 3},
      /* 1000 */ {1, 1},    /* 1001 */ {0, 0}, 
      /* 1010 */ {0, 0},    /* 1011 */ {0, 0},
      /* 1100 */ {1, 2},    /* 1101 */ {0, 0}, 
      /* 1110 */ {1, 3},    /* 1111 */ {0, 0}
    };
    int rc;                       /* Return code of fcntl() */
    struct flock f;               /* Locking operation */





    assert( mRegion<ArraySize(aMap) && aMap[mRegion].iStart!=0 );

    memset(&f, 0, sizeof(f));
    f.l_type = (op==LOG_WRLOCK?F_WRLCK:(op==LOG_RDLOCK?F_RDLCK:F_UNLCK));
    f.l_whence = SEEK_SET;










    f.l_start = 32 + aMap[mRegion].iStart;
    f.l_len = aMap[mRegion].iLen;

    rc = fcntl(pSummary->fd, F_SETLK, &f);
    if( rc!=0 ){
      sqlite3_mutex_leave(pSummary->mutex);
      return SQLITE_BUSY;
    }
  }

  pLog->lock.mLock = mNew;
  sqlite3_mutex_leave(pSummary->mutex);
  return SQLITE_OK;
}

/*
** Try to read the log-summary header. Attempt to verify the header
** checksum. If the checksum can be verified, copy the log-summary
................................................................................
  if( op ){

    /* Obtain the writer lock */
    int rc = logLockRegion(pLog, LOG_REGION_C|LOG_REGION_D, LOG_WRLOCK);
    if( rc!=SQLITE_OK ){
      return rc;
    }

    /* TODO: What if this is a region D reader? And after writing this
    ** transaction it continues to hold a read-lock on the db? Maybe we 
    ** need to switch it to a region A reader here so that unlocking C|D
    ** does not leave the connection with no lock at all.
    */
    assert( pLog->isLocked!=LOG_REGION_D );

    if( memcmp(&pLog->hdr, pLog->pSummary->aData, sizeof(pLog->hdr)) ){
      return SQLITE_BUSY;
    }
    pLog->isWriteLocked = 1;

  }else if( pLog->isWriteLocked ){