/ Check-in [2dd36ade]
Login

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

Overview
Comment:Ensure that write-locks on pages are dropped at the end of each write transaction, even if there is still a read transaction open.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | server-process-edition
Files: files | file ages | folders
SHA3-256:2dd36ade9ea948de27b217b83cbf704e071e6ea5dddb8566165a645143b0f846
User & Date: dan 2017-08-19 15:50:45
Context
2017-08-29
17:52
Add a Tcl script to run the performance tests described in README-server-edition.html. check-in: 1b3df8ff user: dan tags: server-process-edition
2017-08-19
15:50
Ensure that write-locks on pages are dropped at the end of each write transaction, even if there is still a read transaction open. check-in: 2dd36ade user: dan tags: server-process-edition
2017-08-18
18:55
Add tests to this branch. check-in: abb6e076 user: dan tags: server-process-edition
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/pager.c.

2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
  if( rc==SQLITE_OK && bCommit && isOpen(pPager->fd) ){
    rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0);
    if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
  }

#ifdef SQLITE_SERVER_EDITION
  if( pagerIsServer(pPager) ){
    rc2 = sqlite3ServerReleaseWriteLocks(pPager->pServer);
  }else
#endif
  if( !pPager->exclusiveMode 
   && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
  ){
    rc2 = pagerUnlockDb(pPager, SHARED_LOCK);
    pPager->changeCountDone = 0;







|







2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
  if( rc==SQLITE_OK && bCommit && isOpen(pPager->fd) ){
    rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_COMMIT_PHASETWO, 0);
    if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
  }

#ifdef SQLITE_SERVER_EDITION
  if( pagerIsServer(pPager) ){
    rc2 = sqlite3ServerEndWrite(pPager->pServer);
  }else
#endif
  if( !pPager->exclusiveMode 
   && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
  ){
    rc2 = pagerUnlockDb(pPager, SHARED_LOCK);
    pPager->changeCountDone = 0;

Changes to src/server.c.

619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661

662
663
664

665
666
667
668
669
670
671
...
692
693
694
695
696
697
698
699


























700
701
702
703
704
705
706
707
708
709
710
711
...
782
783
784
785
786
787
788
789
790




























791
792
793
794
795
796
797
798
...
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
      if( serverCompareAndSwap(pSlot, o, n) ) break;
    }
  }

  p->nLock = 0;
}

/*
** End a transaction (and release all locks). This version runs in
** single process mode only.
*/
static void serverEndSingle(Server *p){
  Server **pp;
  ServerDb *pDb = p->pDb;
  ServerPage *pPg = 0;

  assert( p->eTrans!=SERVER_TRANS_NONE );
  assert( pDb->pServerShm==0 );

  sqlite3_mutex_enter(pDb->mutex);

  if( p->eTrans==SERVER_TRANS_READONLY ){
    /* Remove the connection from the readers list */
    for(pp=&pDb->pReader; *pp!=p; pp = &((*pp)->pNext));
    *pp = p->pNext;
  }else{
    serverReleaseLocks(p);

    /* Clear the bit in the transaction mask. */
    pDb->transmask &= ~((u32)1 << p->iTransId);

    /* If this connection is in the committers list, remove it. */
    for(pp=&pDb->pCommit; *pp; pp = &((*pp)->pNext)){
      if( *pp==p ){
        *pp = p->pNext;
        break;
      }
    }
  }

  /* See if it is possible to free any ServerPage records. If so, remove
  ** them from the linked list and hash table, but do not call sqlite3_free()
  ** on them until the mutex has been released.  */

  if( pDb->pPgFirst ){
    ServerPage *pLast = 0;
    Server *pIter;

    int iOldest = 0x7FFFFFFF;
    for(pIter=pDb->pReader; pIter; pIter=pIter->pNext){
      iOldest = MIN(iOldest, pIter->iCommitId);
    }
    for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){
      iOldest = MIN(iOldest, pIter->iCommitId);
    }
................................................................................

    if( pPg==0 ){
      pDb->pPgFirst = pDb->pPgLast = 0;
    }else{
      pDb->pPgFirst = pPg;
    }
  }



























  sqlite3_mutex_leave(pDb->mutex);

  p->pNext = 0;
  p->iTransId = -1;
  p->iCommitId = 0;
}

/*
** End a transaction (and release all locks).
*/
int sqlite3ServerEnd(Server *p){
  if( p->eTrans!=SERVER_TRANS_NONE ){
................................................................................
  sqlite3_mutex_leave(pDb->mutex);
  return rc;
}

/*
** Release all write-locks.
*/
int sqlite3ServerReleaseWriteLocks(Server *p){
  int rc = SQLITE_OK;




























  return rc;
}

static int serverCheckClient(Server *p, int iClient){
  ServerDb *pDb = p->pDb;
  int rc = SQLITE_BUSY_DEADLOCK;
  if( pDb->pServerShm && 0==(pDb->transmask & (1 << iClient)) ){

................................................................................
      p->aLock[p->nLock++] = pgno;
    }
  }

  return rc;
}

int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite){
  assert( 0 );
  return 0;
}

static void serverIncrSlowReader(u32 *pSlot, int n){
  assert( n==1 || n==-1 );
  *pSlot += (n * (1 << HMA_SLOT_RLWL_BITS));
}

void sqlite3ServerReadPage(Server *p, Pgno pgno, u8 **ppData){
  if( p->eTrans==SERVER_TRANS_READONLY ){







<
<
<
<
|
<
<
<
<
<

<
|

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

|
<
>

|

>







 







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




<







 







|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|







 







<
<
<
<
<







619
620
621
622
623
624
625




626





627

628
629



















630
631

632
633
634
635
636
637
638
639
640
641
642
643
...
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701

702
703
704
705
706
707
708
...
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
...
919
920
921
922
923
924
925





926
927
928
929
930
931
932
      if( serverCompareAndSwap(pSlot, o, n) ) break;
    }
  }

  p->nLock = 0;
}





static void serverRecycleBuffers(ServerDb *pDb){





  assert( pDb->pServerShm==0 );

  assert( sqlite3_mutex_held(pDb->mutex) );




















  /* See if it is possible to free any ServerPage records. If so, remove
  ** them from the linked list and hash table, and add them to the pFree

  ** list.  */
  if( pDb->pPgFirst ){
    ServerPage *pPg;
    Server *pIter;
    ServerPage *pLast = 0;
    int iOldest = 0x7FFFFFFF;
    for(pIter=pDb->pReader; pIter; pIter=pIter->pNext){
      iOldest = MIN(iOldest, pIter->iCommitId);
    }
    for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){
      iOldest = MIN(iOldest, pIter->iCommitId);
    }
................................................................................

    if( pPg==0 ){
      pDb->pPgFirst = pDb->pPgLast = 0;
    }else{
      pDb->pPgFirst = pPg;
    }
  }
}

/*
** End a transaction (and release all locks). This version runs in
** single process mode only.
*/
static void serverEndSingle(Server *p){
  Server **pp;
  ServerDb *pDb = p->pDb;

  assert( p->eTrans!=SERVER_TRANS_NONE );
  assert( pDb->pServerShm==0 );

  sqlite3_mutex_enter(pDb->mutex);

  if( p->eTrans==SERVER_TRANS_READONLY ){
    /* Remove the connection from the readers list */
    for(pp=&pDb->pReader; *pp!=p; pp = &((*pp)->pNext));
    *pp = p->pNext;
  }else{
    serverReleaseLocks(p);

    /* Clear the bit in the transaction mask. */
    pDb->transmask &= ~((u32)1 << p->iTransId);
  }

  serverRecycleBuffers(pDb);
  sqlite3_mutex_leave(pDb->mutex);

  p->pNext = 0;
  p->iTransId = -1;

}

/*
** End a transaction (and release all locks).
*/
int sqlite3ServerEnd(Server *p){
  if( p->eTrans!=SERVER_TRANS_NONE ){
................................................................................
  sqlite3_mutex_leave(pDb->mutex);
  return rc;
}

/*
** Release all write-locks.
*/
int sqlite3ServerEndWrite(Server *p){
  ServerDb *pDb = p->pDb;
  int i;

  if( pDb->pServerShm==0 ) sqlite3_mutex_enter(pDb->mutex);
  for(i=0; i<p->nLock; i++){
    while( 1 ){
      u32 *pSlot = serverLockingSlot(pDb, p->aLock[i]);
      u32 o = *pSlot;
      u32 n = o & ~((u32)1 << p->iTransId);
      if( slotGetWriter(n)==p->iTransId ){
        n -= ((p->iTransId + 1) << HMA_MAX_TRANSACTIONID);
        n |= ((u32)1 << p->iTransId);
      }
      if( o==n || serverCompareAndSwap(pSlot, o, n) ) break;
    }
  }
  if( pDb->pServerShm==0 ){
    ServerDb **pp;
    /* If this connection is in the committers list, remove it. */
    for(pp=&pDb->pCommit; *pp; pp = &((*pp)->pNext)){
      if( *pp==p ){
        *pp = p->pNext;
        break;
      }
    }
    p->iCommitId = 0;
    sqlite3_mutex_leave(pDb->mutex);
  }

  return SQLITE_OK;
}

static int serverCheckClient(Server *p, int iClient){
  ServerDb *pDb = p->pDb;
  int rc = SQLITE_BUSY_DEADLOCK;
  if( pDb->pServerShm && 0==(pDb->transmask & (1 << iClient)) ){

................................................................................
      p->aLock[p->nLock++] = pgno;
    }
  }

  return rc;
}






static void serverIncrSlowReader(u32 *pSlot, int n){
  assert( n==1 || n==-1 );
  *pSlot += (n * (1 << HMA_SLOT_RLWL_BITS));
}

void sqlite3ServerReadPage(Server *p, Pgno pgno, u8 **ppData){
  if( p->eTrans==SERVER_TRANS_READONLY ){

Changes to src/server.h.

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
int sqlite3ServerConnect(Pager *pPager, int eServer, Server **ppOut);
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd);

int sqlite3ServerBegin(Server *p, int bReadonly);
int sqlite3ServerPreCommit(Server*, ServerPage*);
int sqlite3ServerEnd(Server *p);

int sqlite3ServerReleaseWriteLocks(Server *p);

int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock);

int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite);

ServerPage *sqlite3ServerBuffer(Server*);

int sqlite3ServerIsSingleProcess(Server*);

/* For "BEGIN READONLY" clients. */
int sqlite3ServerIsReadonly(Server*);
void sqlite3ServerReadPage(Server*, Pgno, u8**);
void sqlite3ServerEndReadPage(Server*, Pgno);

#endif /* SQLITE_SERVER_H */
#endif /* SQLITE_SERVER_EDITION */








|



<
<












34
35
36
37
38
39
40
41
42
43
44


45
46
47
48
49
50
51
52
53
54
55
56
int sqlite3ServerConnect(Pager *pPager, int eServer, Server **ppOut);
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd);

int sqlite3ServerBegin(Server *p, int bReadonly);
int sqlite3ServerPreCommit(Server*, ServerPage*);
int sqlite3ServerEnd(Server *p);

int sqlite3ServerEndWrite(Server *p);

int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock);



ServerPage *sqlite3ServerBuffer(Server*);

int sqlite3ServerIsSingleProcess(Server*);

/* For "BEGIN READONLY" clients. */
int sqlite3ServerIsReadonly(Server*);
void sqlite3ServerReadPage(Server*, Pgno, u8**);
void sqlite3ServerEndReadPage(Server*, Pgno);

#endif /* SQLITE_SERVER_H */
#endif /* SQLITE_SERVER_EDITION */

Changes to test/server2.test.

138
139
140
141
142
143
144



























145
146
147
148
    lsort [glob -nocomplain test.db-journal/*-journal]
  } {test.db-journal/0-journal}
  
  do_test $tn.3.2 {
    db close
    lsort [glob -nocomplain test.db-journal/*-journal]
  } {}



























}

finish_test








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
    lsort [glob -nocomplain test.db-journal/*-journal]
  } {test.db-journal/0-journal}
  
  do_test $tn.3.2 {
    db close
    lsort [glob -nocomplain test.db-journal/*-journal]
  } {}

  #-----------------------------------------------------------------------
  # Test that write-locks are downgraded when a transaction is ended,
  # even if the connection holds an open read statement.
  #
  do_test $tn.4.1 {
    server_sqlite3 db test.db
    server_sqlite3 db2 test.db
    db eval {
      CREATE TABLE t2(a);
      INSERT INTO t2 VALUES('one');
      INSERT INTO t2 VALUES('two');
      INSERT INTO t2 VALUES('three');
      CREATE TABLE t3(k INTEGER PRIMARY KEY, val);
    }

    set res [list]
    db eval { SELECT a FROM t2 ORDER BY rowid } {
      db eval { REPLACE INTO t3 VALUES(1, $a) }
      lappend res [db2 one { SELECT val FROM t3 }]
    }

    set res
  } {one two three}

  catch { db close }
  catch { db2 close }
}

finish_test