Repository: symisc/unqlite (language: rust)

Issue:
Random store failures without any error reported
See symisc/vedis#3. The same code is used in `unqlite`, and the same bugs exist.

There are 3 fixes to be applied: symisc/vedis#5, symisc/vedis#6, symisc/vedis#7

Here is example program to reproduce the issue (it is the vedis version adopted for unqlite)

``` c

#include <unqlite.h>

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// generate random bytes of variable (important!) length
char *randomBytes(int* len)
{
    *len = rand() % 4 + 1;
    char *res = (char*)malloc(*len);

    int i;
    for (i = 0; i < *len; i++)
    {
        res[i] = rand() % 256;
    }

    return res;
}

int main()
{
    unqlite *db;
    int res = unqlite_open(&db, "test.db", UNQLITE_OPEN_CREATE);
    if (res != UNQLITE_OK)
    {
        printf("Error opening unqlite: %i\n", (int)res);
        return 0;
    }

    srand(time(NULL));

    char buffer[255];
    unqlite_int64 inout;

    int block = 0;
    int count = 0;

    while(1)
    {
        int keyLen;
        int valLen;
        char *key = randomBytes(&keyLen);
        char *val = randomBytes(&valLen);

        // insert random key
        res = unqlite_kv_store(db, key, keyLen, val, valLen);
        if (res != UNQLITE_OK)
        {
            printf("Unable to store: %i\n", (int)res);
            return 0;
        }

        // verify it was stored correctly
        inout = 255;
        res = unqlite_kv_fetch(db, key, keyLen, &buffer[0], &inout);
        if (res != UNQLITE_OK)
        {
            printf("Unable to fetch: %i", (int)res);
            return 0;
        }

        // generate new random key and delete if it exists
        free(key);
        key = randomBytes(&keyLen);

        inout = 255;
        res = unqlite_kv_fetch(db, key, keyLen, &buffer[0], &inout);
        if (res == UNQLITE_OK)
        {
            res = unqlite_kv_delete(db, key, keyLen);
            if (res != UNQLITE_OK)
            {
                printf("Unable to delete: %i\n", (int)res);
                return 0;
            }
        }

        free(key);
        free(val);

        count++;
        if (count > 10000)
        {
            block++;
            count = 0;
            printf("%i 10K iterations\n", block);
        }
    }
    return 0;
}
```



Discussion / hints:

Fix patch:
diff --git a/src/lhash_kv.c b/src/lhash_kv.c
index e80b20b..4af5b3e 100644
--- a/src/lhash_kv.c
+++ b/src/lhash_kv.c
@@ -1,6 +1,7 @@
 /*
  * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
  * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
+ * Copyright (C) 2014, Yuras Shumovich <shumovichy@gmail.com>
  * Version 1.1.6
  * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
  * please contact Symisc Systems via:
@@ -1280,6 +1281,7 @@ static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft)
 		SyBigEndianPack16(&zBlock[2],iBlksz-nByte); /* Block size*/
 		/* Offset of the new block */
 		iNext = (sxu16)(zPtr - pPage->pRaw->zData);
+		iBlksz = nByte;
 	}
 	/* Fix offsets */
 	if( zPrev ){
@@ -1291,7 +1293,7 @@ static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft)
 		SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext);
 	}
 	/* All done */
-	pPage->nFree -= nByte;
+	pPage->nFree -= iBlksz;
 	return UNQLITE_OK;
 }
 /*
@@ -2079,18 +2081,22 @@ static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppS
 	/* Look for an already attached slave page */
 	for( i = 0 ; i < pMaster->iSlave ; ++i ){
 		/* Find a free chunk big enough */
-		rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ+nAmount,&iOfft);
+		sxu16 size = L_HASH_CELL_SZ + nAmount;
+		rc = lhAllocateSpace(pSlave,size,&iOfft);
 		if( rc != UNQLITE_OK ){
 			/* A space for cell header only */
-			rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ,&iOfft);
+			size = L_HASH_CELL_SZ;
+			rc = lhAllocateSpace(pSlave,size,&iOfft);
 		}
 		if( rc == UNQLITE_OK ){
 			/* All done */
 			if( pOfft ){
 				*pOfft = iOfft;
+			}else{
+				rc = lhRestoreSpace(pSlave, iOfft, size);
 			}
 			*ppSlave = pSlave;
-			return UNQLITE_OK;
+			return rc;
 		}
 		/* Point to the next slave page */
 		pSlave = pSlave->pNextSlave;
diff --git a/src/pager.c b/src/pager.c
index 702f785..474a1ca 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -1,6 +1,7 @@
 /*
  * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
  * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
+ * Copyright (C) 2014, Yuras Shumovich <shumovichy@gmail.com>
  * Version 1.1.6
  * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
  * please contact Symisc Systems via:
@@ -2683,6 +2684,26 @@ static int unqliteKvIoPageDontMakeHot(unqlite_page *pRaw)
 		return UNQLITE_OK;
 	}
 	pPage->flags |= PAGE_DONT_MAKE_HOT;
+
+	/* Remove from hot dirty list if it is already there */
+	if( pPage->flags & PAGE_HOT_DIRTY ){
+		Pager *pPager = pPage->pPager;
+		if( pPage->pNextHot ){
+			pPage->pNextHot->pPrevHot = pPage->pPrevHot;
+		}
+		if( pPage->pPrevHot ){
+			pPage->pPrevHot->pNextHot = pPage->pNextHot;
+		}
+		if( pPager->pFirstHot == pPage ){
+			pPager->pFirstHot = pPage->pPrevHot;
+		}
+		if( pPager->pHotDirty == pPage ){
+			pPager->pHotDirty = pPage->pNextHot;
+		}
+		pPager->nHot--;
+		pPage->flags &= ~PAGE_HOT_DIRTY;
+	}
+
 	return UNQLITE_OK;
 }
 /* 


Test patch: