Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Enable the disabled asserts added by (5629). Add extra tests to thread003.test. And the required modifications to pcache.c. (CVS 5631) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
473c09fac22ed2f56ea86150a60b9f0f |
User & Date: | danielk1977 2008-08-28 10:21:17.000 |
Context
2008-08-28
| ||
11:12 | Fix mutex related bug in pcache.c to do with handling IO errors. (CVS 5632) (check-in: 5e304fed27 user: danielk1977 tags: trunk) | |
10:21 | Enable the disabled asserts added by (5629). Add extra tests to thread003.test. And the required modifications to pcache.c. (CVS 5631) (check-in: 473c09fac2 user: danielk1977 tags: trunk) | |
08:31 | Fix a threads/mutex problem in pcache.c. (CVS 5630) (check-in: 1928f15b78 user: danielk1977 tags: trunk) | |
Changes
Changes to src/pcache.c.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* ** 2008 August 05 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file implements that page cache. ** | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* ** 2008 August 05 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file implements that page cache. ** ** @(#) $Id: pcache.c,v 1.21 2008/08/28 10:21:17 danielk1977 Exp $ */ #include "sqliteInt.h" /* ** A complete page cache is an instance of this structure. ** ** A cache may only be deleted by its owner and while holding the |
︙ | ︙ | |||
169 170 171 172 173 174 175 | */ static int pcacheCheckSynced(PCache *pCache){ PgHdr *p = pCache->pDirtyTail; for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pPrev){ assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) ); } return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0); | < | | 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | */ static int pcacheCheckSynced(PCache *pCache){ PgHdr *p = pCache->pDirtyTail; for(p=pCache->pDirtyTail; p!=pCache->pSynced; p=p->pPrev){ assert( p->nRef || (p->flags&PGHDR_NEED_SYNC) ); } return (p==0 || p->nRef || (p->flags&PGHDR_NEED_SYNC)==0); } #endif /* !NDEBUG && SQLITE_ENABLE_EXPENSIVE_ASSERT */ /* ** Remove a page from its hash table (PCache.apHash[]). */ static void pcacheRemoveFromHash(PgHdr *pPage){ assert( pcacheMutexHeld() ); if( pPage->pPrevHash ){ pPage->pPrevHash->pNextHash = pPage->pNextHash; }else{ PCache *pCache = pPage->pCache; u32 h = pPage->pgno % pCache->nHash; assert( pCache->apHash[h]==pPage ); pCache->apHash[h] = pPage->pNextHash; |
︙ | ︙ | |||
203 204 205 206 207 208 209 | ** Insert a page into the hash table ** ** The mutex must be held by the caller. */ static void pcacheAddToHash(PgHdr *pPage){ PCache *pCache = pPage->pCache; u32 h = pPage->pgno % pCache->nHash; | | | > | > | 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | ** Insert a page into the hash table ** ** The mutex must be held by the caller. */ static void pcacheAddToHash(PgHdr *pPage){ PCache *pCache = pPage->pCache; u32 h = pPage->pgno % pCache->nHash; assert( pcacheMutexHeld() ); pPage->pNextHash = pCache->apHash[h]; pPage->pPrevHash = 0; if( pCache->apHash[h] ){ pCache->apHash[h]->pPrevHash = pPage; } pCache->apHash[h] = pPage; pCache->nPage++; expensive_assert( pcacheCheckHashCount(pCache) ); } /* ** Attempt to increase the size the hash table to contain ** at least nHash buckets. */ static int pcacheResizeHash(PCache *pCache, int nHash){ PgHdr *p; PgHdr **pNew; assert( pcacheMutexHeld() ); #ifdef SQLITE_MALLOC_SOFT_LIMIT if( nHash*sizeof(PgHdr*)>SQLITE_MALLOC_SOFT_LIMIT ){ nHash = SQLITE_MALLOC_SOFT_LIMIT/sizeof(PgHdr *); } #endif pcacheExitMutex(); pNew = (PgHdr **)sqlite3Malloc(sizeof(PgHdr*)*nHash); pcacheEnterMutex(); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(PgHdr *)*nHash); sqlite3_free(pCache->apHash); pCache->apHash = pNew; pCache->nHash = nHash; |
︙ | ︙ | |||
253 254 255 256 257 258 259 | /* ** Remove a page from a linked list that is headed by *ppHead. ** *ppHead is either PCache.pClean or PCache.pDirty. */ static void pcacheRemoveFromList(PgHdr **ppHead, PgHdr *pPage){ int isDirtyList = (ppHead==&pPage->pCache->pDirty); assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty ); | | | 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | /* ** Remove a page from a linked list that is headed by *ppHead. ** *ppHead is either PCache.pClean or PCache.pDirty. */ static void pcacheRemoveFromList(PgHdr **ppHead, PgHdr *pPage){ int isDirtyList = (ppHead==&pPage->pCache->pDirty); assert( ppHead==&pPage->pCache->pClean || ppHead==&pPage->pCache->pDirty ); assert( pcacheMutexHeld() || ppHead!=&pPage->pCache->pClean ); if( pPage->pPrev ){ pPage->pPrev->pNext = pPage->pNext; }else{ assert( *ppHead==pPage ); *ppHead = pPage->pNext; } |
︙ | ︙ | |||
539 540 541 542 543 544 545 | static int pcacheRecycleOrAlloc(PCache *pCache, PgHdr **ppPage){ PgHdr *p = 0; int szPage = pCache->szPage; int szExtra = pCache->szExtra; assert( pcache.isInit ); | | < | 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 | static int pcacheRecycleOrAlloc(PCache *pCache, PgHdr **ppPage){ PgHdr *p = 0; int szPage = pCache->szPage; int szExtra = pCache->szExtra; assert( pcache.isInit ); assert( sqlite3_mutex_held(pcache.mutex) ); *ppPage = 0; /* If we have reached the limit for pinned/dirty pages, and there is at ** least one dirty page, invoke the xStress callback to cause a page to ** become clean. */ expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); expensive_assert( pcacheCheckSynced(pCache) ); |
︙ | ︙ | |||
590 591 592 593 594 595 596 | p = 0; } if( !p ){ p = pcachePageAlloc(pCache); } | < | 590 591 592 593 594 595 596 597 598 599 600 601 602 603 | p = 0; } if( !p ){ p = pcachePageAlloc(pCache); } *ppPage = p; return (p?SQLITE_OK:SQLITE_NOMEM); } /*************************************************** General Interfaces ****** ** ** Initialize and shutdown the page cache subsystem. Neither of these |
︙ | ︙ | |||
672 673 674 675 676 677 678 | */ int sqlite3PcacheFetch( PCache *pCache, /* Obtain the page from this cache */ Pgno pgno, /* Page number to obtain */ int createFlag, /* If true, create page if it does not exist already */ PgHdr **ppPage /* Write the page here */ ){ | > | > > > < < < | < | < < < | < | | < < < > | | | | | | | | | | < < | | > > > > > | | 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 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 | */ int sqlite3PcacheFetch( PCache *pCache, /* Obtain the page from this cache */ Pgno pgno, /* Page number to obtain */ int createFlag, /* If true, create page if it does not exist already */ PgHdr **ppPage /* Write the page here */ ){ int rc = SQLITE_OK; PgHdr *pPage = 0; assert( pcache.isInit ); assert( pCache!=0 ); assert( pgno>0 ); expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); pcacheEnterMutex(); /* Search the hash table for the requested page. Exit early if it is found. */ if( pCache->apHash ){ u32 h = pgno % pCache->nHash; for(pPage=pCache->apHash[h]; pPage; pPage=pPage->pNextHash){ if( pPage->pgno==pgno ){ if( pPage->nRef==0 ){ if( 0==(pPage->flags&PGHDR_DIRTY) ){ pcacheRemoveFromLruList(pPage); pCache->nPinned++; } pCache->nRef++; } pPage->nRef++; break; } } } if( !pPage && createFlag ){ if( pCache->nHash<=pCache->nPage ){ rc = pcacheResizeHash(pCache, pCache->nHash<256 ? 256 : pCache->nHash*2); } if( rc==SQLITE_OK ){ rc = pcacheRecycleOrAlloc(pCache, &pPage); } if( rc==SQLITE_OK ){ pPage->pPager = 0; pPage->flags = 0; pPage->pDirty = 0; pPage->pgno = pgno; pPage->pCache = pCache; pPage->nRef = 1; pCache->nRef++; pCache->nPinned++; pcacheAddToList(&pCache->pClean, pPage); pcacheAddToHash(pPage); } } pcacheExitMutex(); *ppPage = pPage; expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); assert( pPage || !createFlag || rc!=SQLITE_OK ); return rc; } /* ** Dereference a page. When the reference count reaches zero, ** move the page to the LRU list if it is clean. */ void sqlite3PcacheRelease(PgHdr *p){ |
︙ | ︙ | |||
775 776 777 778 779 780 781 782 783 | void sqlite3PcacheDrop(PgHdr *p){ PCache *pCache; assert( p->nRef==1 ); assert( 0==(p->flags&PGHDR_DIRTY) ); pCache = p->pCache; pCache->nRef--; pCache->nPinned--; pcacheRemoveFromList(&pCache->pClean, p); pcacheRemoveFromHash(p); | > < > > < < < < | | < < < > > > > > > > > > > | > | 771 772 773 774 775 776 777 778 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 824 825 826 827 828 829 830 831 832 833 | void sqlite3PcacheDrop(PgHdr *p){ PCache *pCache; assert( p->nRef==1 ); assert( 0==(p->flags&PGHDR_DIRTY) ); pCache = p->pCache; pCache->nRef--; pCache->nPinned--; pcacheEnterMutex(); pcacheRemoveFromList(&pCache->pClean, p); pcacheRemoveFromHash(p); pcachePageFree(p); pcacheExitMutex(); } /* ** Make sure the page is marked as dirty. If it isn't dirty already, ** make it so. */ void sqlite3PcacheMakeDirty(PgHdr *p){ PCache *pCache; p->flags &= ~PGHDR_DONT_WRITE; if( p->flags & PGHDR_DIRTY ) return; assert( (p->flags & PGHDR_DIRTY)==0 ); assert( p->nRef>0 ); pCache = p->pCache; pcacheEnterMutex(); pcacheRemoveFromList(&pCache->pClean, p); pcacheAddToList(&pCache->pDirty, p); pcacheExitMutex(); p->flags |= PGHDR_DIRTY; } void pcacheMakeClean(PgHdr *p){ PCache *pCache = p->pCache; assert( p->apSave[0]==0 && p->apSave[1]==0 ); assert( p->flags & PGHDR_DIRTY ); pcacheRemoveFromList(&pCache->pDirty, p); pcacheAddToList(&pCache->pClean, p); p->flags &= ~PGHDR_DIRTY; if( p->nRef==0 ){ pcacheAddToLruList(p); pCache->nPinned--; } expensive_assert( pCache->nPinned==pcachePinnedCount(pCache) ); } /* ** Make sure the page is marked as clean. If it isn't clean already, ** make it so. */ void sqlite3PcacheMakeClean(PgHdr *p){ if( (p->flags & PGHDR_DIRTY) ){ pcacheEnterMutex(); pcacheMakeClean(p); pcacheExitMutex(); } } /* ** Make every page in the cache clean. */ void sqlite3PcacheCleanAll(PCache *pCache){ PgHdr *p; |
︙ | ︙ | |||
848 849 850 851 852 853 854 855 856 857 858 | /* ** Change the page number of page p to newPgno. If newPgno is 0, then the ** page object is added to the clean-list and the PGHDR_REUSE_UNLIKELY ** flag set. */ void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ assert( p->nRef>0 ); pcacheRemoveFromHash(p); p->pgno = newPgno; if( newPgno==0 ){ p->flags |= PGHDR_REUSE_UNLIKELY; | > < < > | | > > | 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 | /* ** Change the page number of page p to newPgno. If newPgno is 0, then the ** page object is added to the clean-list and the PGHDR_REUSE_UNLIKELY ** flag set. */ void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ assert( p->nRef>0 ); pcacheEnterMutex(); pcacheRemoveFromHash(p); p->pgno = newPgno; if( newPgno==0 ){ p->flags |= PGHDR_REUSE_UNLIKELY; pcacheFree(p->apSave[0]); pcacheFree(p->apSave[1]); p->apSave[0] = 0; p->apSave[1] = 0; if( (p->flags & PGHDR_DIRTY) ){ pcacheMakeClean(p); } } pcacheAddToHash(p); pcacheExitMutex(); } /* ** Remove all content from a page cache */ void pcacheClear(PCache *pCache){ PgHdr *p, *pNext; |
︙ | ︙ |
Changes to test/thread003.test.
︙ | ︙ | |||
8 9 10 11 12 13 14 | # May you share freely, never taking more than you give. # #*********************************************************************** # # This file contains tests that attempt to break the pcache module # by bombarding it with simultaneous requests from multiple threads. # | | | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # May you share freely, never taking more than you give. # #*********************************************************************** # # This file contains tests that attempt to break the pcache module # by bombarding it with simultaneous requests from multiple threads. # # $Id: thread003.test,v 1.2 2008/08/28 10:21:17 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/thread_common.tcl if {[info commands sqlthread] eq ""} { finish_test |
︙ | ︙ | |||
102 103 104 105 106 107 108 109 110 111 112 113 114 115 | unset -nocomplain finished($zFile) thread_spawn finished($zFile) $thread_procs $SCRIPT } foreach zFile {test.db test2.db} { if {![info exists finished($zFile)]} { vwait finished($zFile) } } expr 0 } {0} finish_test | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | unset -nocomplain finished($zFile) thread_spawn finished($zFile) $thread_procs $SCRIPT } foreach zFile {test.db test2.db} { if {![info exists finished($zFile)]} { vwait finished($zFile) } } expr 0 } {0} # This test is the same as the test above, except that each thread also # writes to the database. This causes pages to be moved back and forth # between the caches internal dirty and clean lists, which is another # opportunity for a thread-related bug to present itself. # set nSecond 30 puts "Starting thread003.3 (should run for ~$nSecond seconds)" do_test thread003.3 { foreach zFile {test.db test2.db} { set SCRIPT [format { set iStart [clock seconds] set iEnd [expr {[clock seconds] + %d}] set ::DB [sqlthread open %s] # Set the cache size to 15 pages per cache. 30 available globally. execsql { PRAGMA cache_size = 15 } while {[clock seconds] < $iEnd} { set iQuery [expr {int(rand()*5000)}] execsql "SELECT * FROM t1 WHERE a = $iQuery" execsql "UPDATE t1 SET b = randomblob(200) WHERE a < $iQuery AND a > $iQuery + 20 " } sqlite3_close $::DB expr 1 } $nSecond $zFile] unset -nocomplain finished($zFile) thread_spawn finished($zFile) $thread_procs $SCRIPT } foreach zFile {test.db test2.db} { if {![info exists finished($zFile)]} { vwait finished($zFile) } } expr 0 } {0} # In this test case, one thread is continually querying the database. # The other thread does not have a database connection, but calls # sqlite3_release_memory() over and over again. # set nSecond 30 puts "Starting thread003.3 (should run for ~$nSecond seconds)" do_test thread003.4 { thread_spawn finished(1) $thread_procs [format { set iEnd [expr {[clock seconds] + %d}] set ::DB [sqlthread open test.db] # Set the cache size to 15 pages per cache. 30 available globally. execsql { PRAGMA cache_size = 15 } while {[clock seconds] < $iEnd} { set iQuery [expr {int(rand()*5000)}] execsql "SELECT * FROM t1 WHERE a = $iQuery" } sqlite3_close $::DB expr 1 } $nSecond] thread_spawn finished(2) [format { set iEnd [expr {[clock seconds] + %d}] while {[clock seconds] < $iEnd} { sqlite3_release_memory 1000 } } $nSecond] foreach ii {1 2} { if {![info exists finished($ii)]} { vwait finished($ii) } } expr 0 } {0} finish_test |