| |
| #include "lsmtest.h" |
| |
| typedef struct OomTest OomTest; |
| struct OomTest { |
| lsm_env *pEnv; |
| int iNext; /* Next value to pass to testMallocOom() */ |
| int nFail; /* Number of OOM events injected */ |
| int bEnable; |
| int rc; /* Test case error code */ |
| }; |
| |
| static void testOomStart(OomTest *p){ |
| memset(p, 0, sizeof(OomTest)); |
| p->iNext = 1; |
| p->bEnable = 1; |
| p->nFail = 1; |
| p->pEnv = tdb_lsm_env(); |
| } |
| |
| static void xOomHook(OomTest *p){ |
| p->nFail++; |
| } |
| |
| static int testOomContinue(OomTest *p){ |
| if( p->rc!=0 || (p->iNext>1 && p->nFail==0) ){ |
| return 0; |
| } |
| p->nFail = 0; |
| testMallocOom(p->pEnv, p->iNext, 0, (void (*)(void*))xOomHook, (void *)p); |
| return 1; |
| } |
| |
| static void testOomEnable(OomTest *p, int bEnable){ |
| p->bEnable = bEnable; |
| testMallocOomEnable(p->pEnv, bEnable); |
| } |
| |
| static void testOomNext(OomTest *p){ |
| p->iNext++; |
| } |
| |
| static int testOomHit(OomTest *p){ |
| return (p->nFail>0); |
| } |
| |
| static int testOomFinish(OomTest *p){ |
| return p->rc; |
| } |
| |
| static void testOomAssert(OomTest *p, int bVal){ |
| if( bVal==0 ){ |
| test_failed(); |
| p->rc = 1; |
| } |
| } |
| |
| /* |
| ** Test that the error code matches the state of the OomTest object passed |
| ** as the first argument. Specifically, check that rc is LSM_NOMEM if an |
| ** OOM error has already been injected, or LSM_OK if not. |
| */ |
| static void testOomAssertRc(OomTest *p, int rc){ |
| testOomAssert(p, rc==LSM_OK || rc==LSM_NOMEM); |
| testOomAssert(p, testOomHit(p)==(rc==LSM_NOMEM) || p->bEnable==0 ); |
| } |
| |
| static void testOomOpen( |
| OomTest *pOom, |
| const char *zName, |
| lsm_db **ppDb, |
| int *pRc |
| ){ |
| if( *pRc==LSM_OK ){ |
| int rc; |
| rc = lsm_new(tdb_lsm_env(), ppDb); |
| if( rc==LSM_OK ) rc = lsm_open(*ppDb, zName); |
| testOomAssertRc(pOom, rc); |
| *pRc = rc; |
| } |
| } |
| |
| static void testOomFetch( |
| OomTest *pOom, |
| lsm_db *pDb, |
| void *pKey, int nKey, |
| void *pVal, int nVal, |
| int *pRc |
| ){ |
| testOomAssertRc(pOom, *pRc); |
| if( *pRc==LSM_OK ){ |
| lsm_cursor *pCsr; |
| int rc; |
| |
| rc = lsm_csr_open(pDb, &pCsr); |
| if( rc==LSM_OK ) rc = lsm_csr_seek(pCsr, pKey, nKey, 0); |
| testOomAssertRc(pOom, rc); |
| |
| if( rc==LSM_OK ){ |
| const void *p; int n; |
| testOomAssert(pOom, lsm_csr_valid(pCsr)); |
| |
| rc = lsm_csr_key(pCsr, &p, &n); |
| testOomAssertRc(pOom, rc); |
| testOomAssert(pOom, rc!=LSM_OK || (n==nKey && memcmp(pKey, p, nKey)==0) ); |
| } |
| |
| if( rc==LSM_OK ){ |
| const void *p; int n; |
| testOomAssert(pOom, lsm_csr_valid(pCsr)); |
| |
| rc = lsm_csr_value(pCsr, &p, &n); |
| testOomAssertRc(pOom, rc); |
| testOomAssert(pOom, rc!=LSM_OK || (n==nVal && memcmp(pVal, p, nVal)==0) ); |
| } |
| |
| lsm_csr_close(pCsr); |
| *pRc = rc; |
| } |
| } |
| |
| static void testOomWrite( |
| OomTest *pOom, |
| lsm_db *pDb, |
| void *pKey, int nKey, |
| void *pVal, int nVal, |
| int *pRc |
| ){ |
| testOomAssertRc(pOom, *pRc); |
| if( *pRc==LSM_OK ){ |
| int rc; |
| |
| rc = lsm_insert(pDb, pKey, nKey, pVal, nVal); |
| testOomAssertRc(pOom, rc); |
| |
| *pRc = rc; |
| } |
| } |
| |
| |
| static void testOomFetchStr( |
| OomTest *pOom, |
| lsm_db *pDb, |
| const char *zKey, |
| const char *zVal, |
| int *pRc |
| ){ |
| int nKey = strlen(zKey); |
| int nVal = strlen(zVal); |
| testOomFetch(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc); |
| } |
| |
| static void testOomFetchData( |
| OomTest *pOom, |
| lsm_db *pDb, |
| Datasource *pData, |
| int iKey, |
| int *pRc |
| ){ |
| void *pKey; int nKey; |
| void *pVal; int nVal; |
| testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal); |
| testOomFetch(pOom, pDb, pKey, nKey, pVal, nVal, pRc); |
| } |
| |
| static void testOomWriteStr( |
| OomTest *pOom, |
| lsm_db *pDb, |
| const char *zKey, |
| const char *zVal, |
| int *pRc |
| ){ |
| int nKey = strlen(zKey); |
| int nVal = strlen(zVal); |
| testOomWrite(pOom, pDb, (void *)zKey, nKey, (void *)zVal, nVal, pRc); |
| } |
| |
| static void testOomWriteData( |
| OomTest *pOom, |
| lsm_db *pDb, |
| Datasource *pData, |
| int iKey, |
| int *pRc |
| ){ |
| void *pKey; int nKey; |
| void *pVal; int nVal; |
| testDatasourceEntry(pData, iKey, &pKey, &nKey, &pVal, &nVal); |
| testOomWrite(pOom, pDb, pKey, nKey, pVal, nVal, pRc); |
| } |
| |
| static void testOomScan( |
| OomTest *pOom, |
| lsm_db *pDb, |
| int bReverse, |
| const void *pKey, int nKey, |
| int nScan, |
| int *pRc |
| ){ |
| if( *pRc==0 ){ |
| int rc; |
| int iScan = 0; |
| lsm_cursor *pCsr; |
| int (*xAdvance)(lsm_cursor *) = 0; |
| |
| |
| rc = lsm_csr_open(pDb, &pCsr); |
| testOomAssertRc(pOom, rc); |
| |
| if( rc==LSM_OK ){ |
| if( bReverse ){ |
| rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_LE); |
| xAdvance = lsm_csr_prev; |
| }else{ |
| rc = lsm_csr_seek(pCsr, pKey, nKey, LSM_SEEK_GE); |
| xAdvance = lsm_csr_next; |
| } |
| } |
| testOomAssertRc(pOom, rc); |
| |
| while( rc==LSM_OK && lsm_csr_valid(pCsr) && iScan<nScan ){ |
| const void *p; int n; |
| |
| rc = lsm_csr_key(pCsr, &p, &n); |
| testOomAssertRc(pOom, rc); |
| if( rc==LSM_OK ){ |
| rc = lsm_csr_value(pCsr, &p, &n); |
| testOomAssertRc(pOom, rc); |
| } |
| if( rc==LSM_OK ){ |
| rc = xAdvance(pCsr); |
| testOomAssertRc(pOom, rc); |
| } |
| iScan++; |
| } |
| |
| lsm_csr_close(pCsr); |
| *pRc = rc; |
| } |
| } |
| |
| #define LSMTEST6_TESTDB "testdb.lsm" |
| |
| void testDeleteLsmdb(const char *zFile){ |
| char *zLog = testMallocPrintf("%s-log", zFile); |
| char *zShm = testMallocPrintf("%s-shm", zFile); |
| unlink(zFile); |
| unlink(zLog); |
| unlink(zShm); |
| testFree(zLog); |
| testFree(zShm); |
| } |
| |
| static void copy_file(const char *zFrom, const char *zTo, int isDatabase){ |
| |
| if( access(zFrom, F_OK) ){ |
| unlink(zTo); |
| }else{ |
| int fd1; |
| int fd2; |
| off_t sz; |
| off_t i; |
| struct stat buf; |
| u8 *aBuf; |
| |
| fd1 = open(zFrom, O_RDONLY | _O_BINARY, 0644); |
| fd2 = open(zTo, O_RDWR | O_CREAT | _O_BINARY, 0644); |
| |
| fstat(fd1, &buf); |
| sz = buf.st_size; |
| ftruncate(fd2, sz); |
| |
| aBuf = testMalloc(4096); |
| for(i=0; i<sz; i+=4096){ |
| int bLockPage = isDatabase && i == 0; |
| int nByte = MIN((bLockPage ? 4066 : 4096), sz - i); |
| memset(aBuf, 0, 4096); |
| read(fd1, aBuf, nByte); |
| write(fd2, aBuf, nByte); |
| if( bLockPage ){ |
| lseek(fd1, 4096, SEEK_SET); |
| lseek(fd2, 4096, SEEK_SET); |
| } |
| } |
| testFree(aBuf); |
| |
| close(fd1); |
| close(fd2); |
| } |
| } |
| |
| void testCopyLsmdb(const char *zFrom, const char *zTo){ |
| char *zLog1 = testMallocPrintf("%s-log", zFrom); |
| char *zLog2 = testMallocPrintf("%s-log", zTo); |
| char *zShm1 = testMallocPrintf("%s-shm", zFrom); |
| char *zShm2 = testMallocPrintf("%s-shm", zTo); |
| |
| unlink(zShm2); |
| unlink(zLog2); |
| unlink(zTo); |
| copy_file(zFrom, zTo, 1); |
| copy_file(zLog1, zLog2, 0); |
| copy_file(zShm1, zShm2, 0); |
| |
| testFree(zLog1); testFree(zLog2); testFree(zShm1); testFree(zShm2); |
| } |
| |
| /* |
| ** File zFile is the path to a database. This function makes backups |
| ** of the database file and its log as follows: |
| ** |
| ** cp $(zFile) $(zFile)-save |
| ** cp $(zFile)-$(zAux) $(zFile)-save-$(zAux) |
| ** |
| ** Function testRestoreDb() can be used to copy the files back in the |
| ** other direction. |
| */ |
| void testSaveDb(const char *zFile, const char *zAux){ |
| char *zLog = testMallocPrintf("%s-%s", zFile, zAux); |
| char *zFileSave = testMallocPrintf("%s-save", zFile); |
| char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux); |
| |
| unlink(zFileSave); |
| unlink(zLogSave); |
| copy_file(zFile, zFileSave, 1); |
| copy_file(zLog, zLogSave, 0); |
| |
| testFree(zLog); testFree(zFileSave); testFree(zLogSave); |
| } |
| |
| /* |
| ** File zFile is the path to a database. This function restores |
| ** a backup of the database made by a previous call to testSaveDb(). |
| ** Specifically, it does the equivalent of: |
| ** |
| ** cp $(zFile)-save $(zFile) |
| ** cp $(zFile)-save-$(zAux) $(zFile)-$(zAux) |
| */ |
| void testRestoreDb(const char *zFile, const char *zAux){ |
| char *zLog = testMallocPrintf("%s-%s", zFile, zAux); |
| char *zFileSave = testMallocPrintf("%s-save", zFile); |
| char *zLogSave = testMallocPrintf("%s-%s-save", zFile, zAux); |
| |
| copy_file(zFileSave, zFile, 1); |
| copy_file(zLogSave, zLog, 0); |
| |
| testFree(zLog); testFree(zFileSave); testFree(zLogSave); |
| } |
| |
| |
| static int lsmWriteStr(lsm_db *pDb, const char *zKey, const char *zVal){ |
| int nKey = strlen(zKey); |
| int nVal = strlen(zVal); |
| return lsm_insert(pDb, (void *)zKey, nKey, (void *)zVal, nVal); |
| } |
| |
| static void setup_delete_db(void){ |
| testDeleteLsmdb(LSMTEST6_TESTDB); |
| } |
| |
| /* |
| ** Create a small database. With the following content: |
| ** |
| ** "one" -> "one" |
| ** "two" -> "four" |
| ** "three" -> "nine" |
| ** "four" -> "sixteen" |
| ** "five" -> "twentyfive" |
| ** "six" -> "thirtysix" |
| ** "seven" -> "fourtynine" |
| ** "eight" -> "sixtyfour" |
| */ |
| static void setup_populate_db(void){ |
| const char *azStr[] = { |
| "one", "one", |
| "two", "four", |
| "three", "nine", |
| "four", "sixteen", |
| "five", "twentyfive", |
| "six", "thirtysix", |
| "seven", "fourtynine", |
| "eight", "sixtyfour", |
| }; |
| int rc; |
| int ii; |
| lsm_db *pDb; |
| |
| testDeleteLsmdb(LSMTEST6_TESTDB); |
| |
| rc = lsm_new(tdb_lsm_env(), &pDb); |
| if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB); |
| |
| for(ii=0; rc==LSM_OK && ii<ArraySize(azStr); ii+=2){ |
| rc = lsmWriteStr(pDb, azStr[ii], azStr[ii+1]); |
| } |
| lsm_close(pDb); |
| |
| testSaveDb(LSMTEST6_TESTDB, "log"); |
| assert( rc==LSM_OK ); |
| } |
| |
| static Datasource *getDatasource(void){ |
| const DatasourceDefn defn = { TEST_DATASOURCE_RANDOM, 10, 15, 200, 250 }; |
| return testDatasourceNew(&defn); |
| } |
| |
| /* |
| ** Set up a database file with the following properties: |
| ** |
| ** * Page size is 1024 bytes. |
| ** * Block size is 64 KB. |
| ** * Contains 5000 key-value pairs starting at 0 from the |
| ** datasource returned getDatasource(). |
| */ |
| static void setup_populate_db2(void){ |
| Datasource *pData; |
| int ii; |
| int rc; |
| int nBlocksize = 64*1024; |
| int nPagesize = 1024; |
| int nWritebuffer = 4*1024; |
| lsm_db *pDb; |
| |
| testDeleteLsmdb(LSMTEST6_TESTDB); |
| rc = lsm_new(tdb_lsm_env(), &pDb); |
| if( rc==LSM_OK ) rc = lsm_open(pDb, LSMTEST6_TESTDB); |
| |
| lsm_config(pDb, LSM_CONFIG_BLOCK_SIZE, &nBlocksize); |
| lsm_config(pDb, LSM_CONFIG_PAGE_SIZE, &nPagesize); |
| lsm_config(pDb, LSM_CONFIG_AUTOFLUSH, &nWritebuffer); |
| |
| pData = getDatasource(); |
| for(ii=0; rc==LSM_OK && ii<5000; ii++){ |
| void *pKey; int nKey; |
| void *pVal; int nVal; |
| testDatasourceEntry(pData, ii, &pKey, &nKey, &pVal, &nVal); |
| lsm_insert(pDb, pKey, nKey, pVal, nVal); |
| } |
| testDatasourceFree(pData); |
| lsm_close(pDb); |
| |
| testSaveDb(LSMTEST6_TESTDB, "log"); |
| assert( rc==LSM_OK ); |
| } |
| |
| /* |
| ** Test the results of OOM conditions in lsm_new(). |
| */ |
| static void simple_oom_1(OomTest *pOom){ |
| int rc; |
| lsm_db *pDb; |
| |
| rc = lsm_new(tdb_lsm_env(), &pDb); |
| testOomAssertRc(pOom, rc); |
| |
| lsm_close(pDb); |
| } |
| |
| /* |
| ** Test the results of OOM conditions in lsm_open(). |
| */ |
| static void simple_oom_2(OomTest *pOom){ |
| int rc; |
| lsm_db *pDb; |
| |
| rc = lsm_new(tdb_lsm_env(), &pDb); |
| if( rc==LSM_OK ){ |
| rc = lsm_open(pDb, "testdb.lsm"); |
| } |
| testOomAssertRc(pOom, rc); |
| |
| lsm_close(pDb); |
| } |
| |
| /* |
| ** Test the results of OOM conditions in simple fetch operations. |
| */ |
| static void simple_oom_3(OomTest *pOom){ |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| |
| testOomFetchStr(pOom, pDb, "four", "sixteen", &rc); |
| testOomFetchStr(pOom, pDb, "seven", "fourtynine", &rc); |
| testOomFetchStr(pOom, pDb, "one", "one", &rc); |
| testOomFetchStr(pOom, pDb, "eight", "sixtyfour", &rc); |
| |
| lsm_close(pDb); |
| } |
| |
| /* |
| ** Test the results of OOM conditions in simple write operations. |
| */ |
| static void simple_oom_4(OomTest *pOom){ |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| |
| testDeleteLsmdb(LSMTEST6_TESTDB); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| |
| testOomWriteStr(pOom, pDb, "123", "onetwothree", &rc); |
| testOomWriteStr(pOom, pDb, "456", "fourfivesix", &rc); |
| testOomWriteStr(pOom, pDb, "789", "seveneightnine", &rc); |
| testOomWriteStr(pOom, pDb, "123", "teneleventwelve", &rc); |
| testOomWriteStr(pOom, pDb, "456", "fourteenfifteensixteen", &rc); |
| |
| lsm_close(pDb); |
| } |
| |
| static void simple_oom_5(OomTest *pOom){ |
| Datasource *pData = getDatasource(); |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| |
| testRestoreDb(LSMTEST6_TESTDB, "log"); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| |
| testOomFetchData(pOom, pDb, pData, 3333, &rc); |
| testOomFetchData(pOom, pDb, pData, 0, &rc); |
| testOomFetchData(pOom, pDb, pData, 4999, &rc); |
| |
| lsm_close(pDb); |
| testDatasourceFree(pData); |
| } |
| |
| static void simple_oom_6(OomTest *pOom){ |
| Datasource *pData = getDatasource(); |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| |
| testRestoreDb(LSMTEST6_TESTDB, "log"); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| |
| testOomWriteData(pOom, pDb, pData, 5000, &rc); |
| testOomWriteData(pOom, pDb, pData, 5001, &rc); |
| testOomWriteData(pOom, pDb, pData, 5002, &rc); |
| testOomFetchData(pOom, pDb, pData, 5001, &rc); |
| testOomFetchData(pOom, pDb, pData, 1234, &rc); |
| |
| lsm_close(pDb); |
| testDatasourceFree(pData); |
| } |
| |
| static void simple_oom_7(OomTest *pOom){ |
| Datasource *pData = getDatasource(); |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| |
| testRestoreDb(LSMTEST6_TESTDB, "log"); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| testOomScan(pOom, pDb, 0, "abc", 3, 20, &rc); |
| lsm_close(pDb); |
| testDatasourceFree(pData); |
| } |
| |
| static void simple_oom_8(OomTest *pOom){ |
| Datasource *pData = getDatasource(); |
| int rc = LSM_OK; |
| lsm_db *pDb; |
| testRestoreDb(LSMTEST6_TESTDB, "log"); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb, &rc); |
| testOomScan(pOom, pDb, 1, "xyz", 3, 20, &rc); |
| lsm_close(pDb); |
| testDatasourceFree(pData); |
| } |
| |
| /* |
| ** This test case has two clients connected to a database. The first client |
| ** hits an OOM while writing to the database. Check that the second |
| ** connection is still able to query the db following the OOM. |
| */ |
| static void simple_oom2_1(OomTest *pOom){ |
| const int nRecord = 100; /* Number of records initially in db */ |
| const int nIns = 10; /* Number of records inserted with OOM */ |
| |
| Datasource *pData = getDatasource(); |
| int rc = LSM_OK; |
| lsm_db *pDb1; |
| lsm_db *pDb2; |
| int i; |
| |
| testDeleteLsmdb(LSMTEST6_TESTDB); |
| |
| /* Open the two connections. Initialize the in-memory tree so that it |
| ** contains 100 records. Do all this with OOM injection disabled. */ |
| testOomEnable(pOom, 0); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb1, &rc); |
| testOomOpen(pOom, LSMTEST6_TESTDB, &pDb2, &rc); |
| for(i=0; i<nRecord; i++){ |
| testOomWriteData(pOom, pDb1, pData, i, &rc); |
| } |
| testOomEnable(pOom, 1); |
| assert( rc==0 ); |
| |
| /* Insert 10 more records using pDb1. Stop when an OOM is encountered. */ |
| for(i=nRecord; i<nRecord+nIns; i++){ |
| testOomWriteData(pOom, pDb1, pData, i, &rc); |
| if( rc ) break; |
| } |
| testOomAssertRc(pOom, rc); |
| |
| /* Switch off OOM injection. Write a few rows using pDb2. Then check |
| ** that the database may be successfully queried. */ |
| testOomEnable(pOom, 0); |
| rc = 0; |
| for(; i<nRecord+nIns && rc==0; i++){ |
| testOomWriteData(pOom, pDb2, pData, i, &rc); |
| } |
| for(i=0; i<nRecord+nIns; i++) testOomFetchData(pOom, pDb2, pData, i, &rc); |
| testOomEnable(pOom, 1); |
| |
| lsm_close(pDb1); |
| lsm_close(pDb2); |
| testDatasourceFree(pData); |
| } |
| |
| |
| static void do_test_oom1(const char *zPattern, int *pRc){ |
| struct SimpleOom { |
| const char *zName; |
| void (*xSetup)(void); |
| void (*xFunc)(OomTest *); |
| } aSimple[] = { |
| { "oom1.lsm.1", setup_delete_db, simple_oom_1 }, |
| { "oom1.lsm.2", setup_delete_db, simple_oom_2 }, |
| { "oom1.lsm.3", setup_populate_db, simple_oom_3 }, |
| { "oom1.lsm.4", setup_delete_db, simple_oom_4 }, |
| { "oom1.lsm.5", setup_populate_db2, simple_oom_5 }, |
| { "oom1.lsm.6", setup_populate_db2, simple_oom_6 }, |
| { "oom1.lsm.7", setup_populate_db2, simple_oom_7 }, |
| { "oom1.lsm.8", setup_populate_db2, simple_oom_8 }, |
| |
| { "oom2.lsm.1", setup_delete_db, simple_oom2_1 }, |
| }; |
| int i; |
| |
| for(i=0; i<ArraySize(aSimple); i++){ |
| if( *pRc==0 && testCaseBegin(pRc, zPattern, "%s", aSimple[i].zName) ){ |
| OomTest t; |
| |
| if( aSimple[i].xSetup ){ |
| aSimple[i].xSetup(); |
| } |
| |
| for(testOomStart(&t); testOomContinue(&t); testOomNext(&t)){ |
| aSimple[i].xFunc(&t); |
| } |
| |
| printf("(%d injections).", t.iNext-2); |
| testCaseFinish( (*pRc = testOomFinish(&t)) ); |
| testMallocOom(tdb_lsm_env(), 0, 0, 0, 0); |
| } |
| } |
| } |
| |
| void test_oom( |
| const char *zPattern, /* Run test cases that match this pattern */ |
| int *pRc /* IN/OUT: Error code */ |
| ){ |
| do_test_oom1(zPattern, pRc); |
| } |