add atomic testAndSet operation (needed for sherpa tablet names' uniqid field)

git-svn-id: svn+ssh://svn.corp.yahoo.com/yahoo/yrl/labs/pnuts/code/logstore@1158 8dad8b1f-cf64-0410-95b6-bcf113ffbcfe
This commit is contained in:
sears 2010-09-16 22:36:48 +00:00
parent 9ca7d78b06
commit 392a097e44
7 changed files with 173 additions and 13 deletions

View file

@ -603,6 +603,35 @@ void logtable<TUPLE>::insertTuple(datatuple *tuple)
DEBUG("tree size %d tuples %lld bytes.\n", tsize, tree_bytes); DEBUG("tree size %d tuples %lld bytes.\n", tsize, tree_bytes);
} }
template<class TUPLE>
bool logtable<TUPLE>::testAndSetTuple(datatuple *tuple, datatuple *tuple2)
{
bool succ = false;
static pthread_mutex_t test_and_set_mut = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&test_and_set_mut);
datatuple * exists = findTuple_first(-1, tuple2 ? tuple2->key() : tuple->key(), tuple2 ? tuple2->keylen() : tuple->keylen());
if(!tuple2 || tuple2->isDelete()) {
if(!exists || exists->isDelete()) {
succ = true;
} else {
succ = false;
}
} else {
if(tuple2->datalen() == exists->datalen() && !memcmp(tuple2->data(), exists->data(), tuple2->datalen())) {
succ = true;
} else {
succ = false;
}
}
if(exists) datatuple::freetuple(exists);
if(succ) insertTuple(tuple);
pthread_mutex_unlock(&test_and_set_mut);
return succ;
}
template<class TUPLE> template<class TUPLE>
void logtable<TUPLE>::registerIterator(iterator * it) { void logtable<TUPLE>::registerIterator(iterator * it) {
its.push_back(it); its.push_back(it);

View file

@ -49,6 +49,12 @@ private:
public: public:
void insertManyTuples(struct datatuple **tuples, int tuple_count); void insertManyTuples(struct datatuple **tuples, int tuple_count);
void insertTuple(struct datatuple *tuple); void insertTuple(struct datatuple *tuple);
/** This test and set has strange semantics on two fronts:
*
* 1) It is not atomic with respect to non-testAndSet operations (which is fine in theory, since they have no barrier semantics, and we don't have a use case to support the extra overhead)
* 2) If tuple2 is not null, it looks at tuple2's key instead of tuple's key. This means you can atomically set the value of one key based on the value of another (if you want to...)
*/
bool testAndSetTuple(struct datatuple *tuple, struct datatuple *tuple2);
//other class functions //other class functions
recordid allocTable(int xid); recordid allocTable(int xid);

View file

@ -36,21 +36,22 @@ static const network_op_t LOGSTORE_LAST_RESPONSE_CODE = 4;
//client codes //client codes
static const network_op_t LOGSTORE_FIRST_REQUEST_CODE = 8; static const network_op_t LOGSTORE_FIRST_REQUEST_CODE = 8;
static const network_op_t OP_INSERT = 8; // Create, Update, Delete static const network_op_t OP_INSERT = 8; // Create, Update, Delete
static const network_op_t OP_FIND = 9; // Read static const network_op_t OP_TEST_AND_SET = 9; // Create, Update, Delete iff the datatuple matches the second tuple passed in. (or if it doesn't exist, and isDelete() == true.
static const network_op_t OP_SCAN = 10; static const network_op_t OP_FIND = 10; // Read
static const network_op_t OP_BULK_INSERT = 11; static const network_op_t OP_SCAN = 11;
static const network_op_t OP_DONE = 12; // Please close the connection. static const network_op_t OP_BULK_INSERT = 12;
static const network_op_t OP_FLUSH = 13; static const network_op_t OP_DONE = 13; // Please close the connection.
static const network_op_t OP_SHUTDOWN = 14; static const network_op_t OP_FLUSH = 14;
static const network_op_t OP_STAT_SPACE_USAGE = 15; static const network_op_t OP_SHUTDOWN = 15;
static const network_op_t OP_STAT_PERF_REPORT = 16; static const network_op_t OP_STAT_SPACE_USAGE = 16;
static const network_op_t OP_STAT_HISTOGRAM = 17; // Return N approximately equal size partitions (including split points + cardinalities) N=1 estimates table cardinality. static const network_op_t OP_STAT_PERF_REPORT = 17;
static const network_op_t OP_STAT_HISTOGRAM = 18; // Return N approximately equal size partitions (including split points + cardinalities) N=1 estimates table cardinality.
static const network_op_t OP_DBG_DROP_DATABASE = 18; static const network_op_t OP_DBG_DROP_DATABASE = 19;
static const network_op_t OP_DBG_BLOCKMAP = 19; static const network_op_t OP_DBG_BLOCKMAP = 20;
static const network_op_t OP_DBG_NOOP = 20; static const network_op_t OP_DBG_NOOP = 21;
static const network_op_t LOGSTORE_LAST_REQUEST_CODE = 20; static const network_op_t LOGSTORE_LAST_REQUEST_CODE = 21;
//error codes //error codes
static const network_op_t LOGSTORE_FIRST_ERROR = 27; static const network_op_t LOGSTORE_FIRST_ERROR = 27;

View file

@ -15,6 +15,13 @@ inline int requestDispatch<HANDLE>::op_insert(logtable<datatuple> * ltable, HAND
return writeoptosocket(fd, LOGSTORE_RESPONSE_SUCCESS); return writeoptosocket(fd, LOGSTORE_RESPONSE_SUCCESS);
} }
template<class HANDLE> template<class HANDLE>
inline int requestDispatch<HANDLE>::op_test_and_set(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple, datatuple * tuple2) {
//insert/update/delete
bool succ = ltable->testAndSetTuple(tuple, tuple2);
//step 4: send response
return writeoptosocket(fd, succ ? LOGSTORE_RESPONSE_SUCCESS : LOGSTORE_RESPONSE_FAIL);
}
template<class HANDLE>
inline int requestDispatch<HANDLE>::op_bulk_insert(logtable<datatuple> *ltable, HANDLE fd) { inline int requestDispatch<HANDLE>::op_bulk_insert(logtable<datatuple> *ltable, HANDLE fd) {
int err = writeoptosocket(fd, LOGSTORE_RESPONSE_RECEIVING_TUPLES); int err = writeoptosocket(fd, LOGSTORE_RESPONSE_RECEIVING_TUPLES);
datatuple ** tups = (datatuple **) malloc(sizeof(tups[0]) * 100); datatuple ** tups = (datatuple **) malloc(sizeof(tups[0]) * 100);
@ -459,6 +466,10 @@ int requestDispatch<HANDLE>::dispatch_request(network_op_t opcode, datatuple * t
{ {
err = op_insert(ltable, fd, tuple); err = op_insert(ltable, fd, tuple);
} }
else if(opcode == OP_TEST_AND_SET)
{
err = op_test_and_set(ltable, fd, tuple, tuple2);
}
else if(opcode == OP_FIND) else if(opcode == OP_FIND)
{ {
err = op_find(ltable, fd, tuple); err = op_find(ltable, fd, tuple);

View file

@ -14,6 +14,7 @@ template<class HANDLE>
class requestDispatch { class requestDispatch {
private: private:
static inline int op_insert(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple); static inline int op_insert(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple);
static inline int op_test_and_set(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple, datatuple * tuple2);
static inline int op_find(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple); static inline int op_find(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple);
static inline int op_scan(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple, datatuple * tuple2, size_t limit); static inline int op_scan(logtable<datatuple> * ltable, HANDLE fd, datatuple * tuple, datatuple * tuple2, size_t limit);
static inline int op_bulk_insert(logtable<datatuple> * ltable, HANDLE fd); static inline int op_bulk_insert(logtable<datatuple> * ltable, HANDLE fd);

View file

@ -1,5 +1,6 @@
IF( HAVE_STASIS ) IF( HAVE_STASIS )
CREATE_CHECK(check_gen) CREATE_CHECK(check_gen)
CREATE_CHECK(check_testAndSet)
CREATE_CHECK(check_logtree) CREATE_CHECK(check_logtree)
CREATE_CHECK(check_datapage) CREATE_CHECK(check_datapage)
CREATE_CHECK(check_logtable) CREATE_CHECK(check_logtable)

111
test/check_testAndSet.cpp Normal file
View file

@ -0,0 +1,111 @@
/*
* check_testAndSet.cpp
*
* Created on: Sep 16, 2010
* Author: sears
*/
#include <string>
#include <vector>
#include <iostream>
#include <sstream>
#include "logstore.h"
#include "datapage.h"
#include "merger.h"
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <sys/time.h>
#include <time.h>
#include <stasis/transactional.h>
#undef begin
#undef end
#include "check_util.h"
#define NUM_THREADS 128
unsigned char vals[NUM_THREADS];
logtable<datatuple>* ltbl;
int myucharcmp(const void * ap, const void * bp) {
unsigned char a = *(unsigned char*)ap;
unsigned char b = *(unsigned char*)bp;
return (int)a - (int)b;
}
void * worker(void * idp) {
unsigned char id = *(unsigned char*)idp;
bool succ = false;
while(!succ) {
unsigned char key = random() % NUM_THREADS;
printf("id = %d key = %d\n", (int)id, (int)key);
datatuple * dt = datatuple::create(&key, sizeof(key), &id, sizeof(id));
datatuple * dtdelete = datatuple::create(&key, sizeof(key));
succ = ltbl->testAndSetTuple(dt, dtdelete);
datatuple::freetuple(dt);
datatuple::freetuple(dtdelete);
vals[id] = key;
}
return 0;
}
void insertProbeIter(size_t NUM_ENTRIES)
{
srand(1000);
unlink("storefile.txt");
unlink("logfile.txt");
logtable<datatuple>::init_stasis();
int xid = Tbegin();
merge_scheduler mscheduler;
logtable<datatuple> ltable(1000, 10000, 5);
ltbl = &ltable;
recordid table_root = ltable.allocTable(xid);
Tcommit(xid);
int lindex = mscheduler.addlogtable(&ltable);
ltable.setMergeData(mscheduler.getMergeData(lindex));
mscheduler.startlogtable(lindex, 10 * 1024 * 1024);
pthread_t *threads = (pthread_t*)malloc(NUM_THREADS * sizeof(pthread_t));
for(int i = 0; i < NUM_THREADS; i++) {
unsigned char * x = (unsigned char*)malloc(sizeof(unsigned char));
*x = i;
int err = pthread_create(&threads[i], 0, worker, x);
if(err) { errno = err; perror("Couldn't spawn thread"); abort(); }
}
for(int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], 0);
}
qsort(vals, NUM_THREADS, sizeof(unsigned char), &myucharcmp);
for(int i = 0; i < NUM_THREADS; i++) {
assert(((unsigned char)i) == vals[i]);
}
mscheduler.shutdown();
logtable<datatuple>::deinit_stasis();
printf("\npass\n");
}
/** @test
*/
int main()
{
insertProbeIter(5000);
return 0;
}