Overhaul driver to avoid use of pipes and simplify memory management
This commit is contained in:
parent
f20e46d756
commit
f56351e1dc
2 changed files with 212 additions and 215 deletions
|
@ -39,10 +39,11 @@ static int alloc_dbref();
|
||||||
|
|
||||||
static void* zalloc(unsigned int size);
|
static void* zalloc(unsigned int size);
|
||||||
|
|
||||||
static void signal_port(PortData* d);
|
|
||||||
static void* deadlock_check(void* arg);
|
static void* deadlock_check(void* arg);
|
||||||
static void* trickle_write(void* arg);
|
static void* trickle_write(void* arg);
|
||||||
|
|
||||||
|
static void send_ok_or_error(ErlDrvPort port, ErlDrvTermData pid, int rc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global instance of DB_ENV; only a single one exists per O/S process.
|
* Global instance of DB_ENV; only a single one exists per O/S process.
|
||||||
*/
|
*/
|
||||||
|
@ -123,6 +124,18 @@ static TPool* G_TPOOL_TXNS;
|
||||||
bin_helper_push_int32(&bh, val); \
|
bin_helper_push_int32(&bh, val); \
|
||||||
RETURN_BH(bh, outbuf); }
|
RETURN_BH(bh, outbuf); }
|
||||||
|
|
||||||
|
#define FAIL_IF_ASYNC_PENDING(d, outbuf) { \
|
||||||
|
erl_drv_mutex_lock(d->port_lock); \
|
||||||
|
if (d->async_op != CMD_NONE) { \
|
||||||
|
erl_drv_mutex_unlock(d->port_lock); \
|
||||||
|
RETURN_INT(ERROR_ASYNC_PENDING, outbuf); \
|
||||||
|
} else { \
|
||||||
|
erl_drv_mutex_unlock(d->port_lock); \
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DRIVER_INIT(bdberl_drv)
|
DRIVER_INIT(bdberl_drv)
|
||||||
{
|
{
|
||||||
printf("DRIVER INIT\n");
|
printf("DRIVER INIT\n");
|
||||||
|
@ -210,12 +223,15 @@ static ErlDrvData bdberl_drv_start(ErlDrvPort port, char* buffer)
|
||||||
// Save handle to the port
|
// Save handle to the port
|
||||||
d->port = port;
|
d->port = port;
|
||||||
|
|
||||||
// Setup a pair of pipes for notification purposes
|
// Allocate a mutex for the port
|
||||||
assert(pipe(d->pipe_fds) == 0);
|
d->port_lock = erl_drv_mutex_create("bdberl_port_lock");
|
||||||
|
|
||||||
// Make sure both pipes are configured non-blocking
|
// Save the caller/owner PID
|
||||||
assert(fcntl(d->pipe_fds[0], F_SETFL, O_NONBLOCK) == 0);
|
d->port_owner = driver_connected(port);
|
||||||
assert(fcntl(d->pipe_fds[1], F_SETFL, O_NONBLOCK) == 0);
|
|
||||||
|
// Allocate an initial buffer for work purposes
|
||||||
|
d->work_buffer = driver_alloc(4096);
|
||||||
|
d->work_buffer_sz = 4096;
|
||||||
|
|
||||||
// Make sure port is running in binary mode
|
// Make sure port is running in binary mode
|
||||||
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
|
||||||
|
@ -226,20 +242,31 @@ static ErlDrvData bdberl_drv_start(ErlDrvPort port, char* buffer)
|
||||||
static void bdberl_drv_stop(ErlDrvData handle)
|
static void bdberl_drv_stop(ErlDrvData handle)
|
||||||
{
|
{
|
||||||
PortData* d = (PortData*)handle;
|
PortData* d = (PortData*)handle;
|
||||||
// printf("%p: stop\n", d->port);
|
|
||||||
|
// Grab the port lock, in case we have an async job running
|
||||||
|
erl_drv_mutex_lock(d->port_lock);
|
||||||
|
|
||||||
// If there is an async job pending, we need to cancel it. The cancel operation will
|
// If there is an async job pending, we need to cancel it. The cancel operation will
|
||||||
// block until the job has either been removed or has run
|
// block until the job has either been removed or has run
|
||||||
if (d->async_job)
|
if (d->async_job)
|
||||||
{
|
{
|
||||||
|
// Drop the lock prior to starting the wait for the async process
|
||||||
|
erl_drv_mutex_unlock(d->port_lock);
|
||||||
|
|
||||||
printf("Cancelling async job for port: %p\n", d->port);
|
printf("Cancelling async job for port: %p\n", d->port);
|
||||||
bdberl_tpool_cancel(d->async_pool, d->async_job);
|
bdberl_tpool_cancel(d->async_pool, d->async_job);
|
||||||
driver_select(d->port, (ErlDrvEvent)(size_t)d->pipe_fds[0], DO_READ, 0);
|
|
||||||
printf("Canceled async job for port: %p\n", d->port);
|
printf("Canceled async job for port: %p\n", d->port);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there was no async job, drop the lock -- not needed
|
||||||
|
erl_drv_mutex_unlock(d->port_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup the port lock
|
||||||
|
erl_drv_mutex_destroy(d->port_lock);
|
||||||
|
|
||||||
// If a txn is currently active, terminate it. We _must_ do it synchronously (unfortunately) as
|
// If a txn is currently active, terminate it.
|
||||||
// there doesn't seem to be a to do an async op while stopping the driver.
|
|
||||||
if (d->txn)
|
if (d->txn)
|
||||||
{
|
{
|
||||||
d->txn->abort(d->txn);
|
d->txn->abort(d->txn);
|
||||||
|
@ -251,12 +278,10 @@ static void bdberl_drv_stop(ErlDrvData handle)
|
||||||
close_database(d->dbrefs->dbref, 0, d);
|
close_database(d->dbrefs->dbref, 0, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
close(d->pipe_fds[0]);
|
|
||||||
close(d->pipe_fds[1]);
|
|
||||||
printf("Stopped port: %p\n", d->port);
|
printf("Stopped port: %p\n", d->port);
|
||||||
|
|
||||||
// Release the port instance data
|
// Release the port instance data
|
||||||
|
driver_free(d->work_buffer);
|
||||||
driver_free(handle);
|
driver_free(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +347,13 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
}
|
}
|
||||||
case CMD_CLOSE_DB:
|
case CMD_CLOSE_DB:
|
||||||
{
|
{
|
||||||
// TODO: If data is inflight, fail. Abort any open txns.
|
FAIL_IF_ASYNC_PENDING(d, outbuf);
|
||||||
|
|
||||||
|
// Fail if a txn is open
|
||||||
|
if (d->txn != 0)
|
||||||
|
{
|
||||||
|
RETURN_INT(ERROR_TXN_OPEN, outbuf);
|
||||||
|
}
|
||||||
|
|
||||||
// Take the provided dbref and attempt to close it
|
// Take the provided dbref and attempt to close it
|
||||||
// Inbuf is: <<DbRef:32, Flags:32/unsigned>>
|
// Inbuf is: <<DbRef:32, Flags:32/unsigned>>
|
||||||
|
@ -336,11 +367,7 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
}
|
}
|
||||||
case CMD_TXN_BEGIN:
|
case CMD_TXN_BEGIN:
|
||||||
{
|
{
|
||||||
// If an async operation is pending, fail
|
FAIL_IF_ASYNC_PENDING(d, outbuf);
|
||||||
if (d->async_op != CMD_NONE)
|
|
||||||
{
|
|
||||||
RETURN_INT(ERROR_ASYNC_PENDING, outbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we already have a txn open, fail
|
// If we already have a txn open, fail
|
||||||
if (d->txn != 0)
|
if (d->txn != 0)
|
||||||
|
@ -358,11 +385,7 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
case CMD_TXN_COMMIT:
|
case CMD_TXN_COMMIT:
|
||||||
case CMD_TXN_ABORT:
|
case CMD_TXN_ABORT:
|
||||||
{
|
{
|
||||||
// If an async operation is pending, fail
|
FAIL_IF_ASYNC_PENDING(d, outbuf);
|
||||||
if (d->async_op != CMD_NONE)
|
|
||||||
{
|
|
||||||
RETURN_INT(ERROR_ASYNC_PENDING, outbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't already have a txn open, fail
|
// If we don't already have a txn open, fail
|
||||||
if (d->txn == 0)
|
if (d->txn == 0)
|
||||||
|
@ -370,26 +393,14 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
RETURN_INT(ERROR_NO_TXN, outbuf);
|
RETURN_INT(ERROR_NO_TXN, outbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate operation structure
|
// Setup async command and schedule it on the txns threadpool
|
||||||
AsyncData* adata = zalloc(sizeof(AsyncData));
|
d->async_op = cmd;
|
||||||
adata->port = d;
|
|
||||||
|
|
||||||
if (cmd == CMD_TXN_COMMIT)
|
if (cmd == CMD_TXN_COMMIT)
|
||||||
{
|
{
|
||||||
adata->payload = (void*)(size_t)UNPACK_INT(inbuf, 0);
|
d->async_flags = UNPACK_INT(inbuf, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update port data to indicate we have an operation in progress
|
|
||||||
d->async_op = cmd;
|
|
||||||
|
|
||||||
// Schedule async operation to execute the commit/abort
|
|
||||||
d->async_data = adata;
|
|
||||||
d->async_pool = G_TPOOL_TXNS;
|
d->async_pool = G_TPOOL_TXNS;
|
||||||
d->async_job = bdberl_tpool_run(G_TPOOL_TXNS, &do_async_txnop, adata, 0);
|
d->async_job = bdberl_tpool_run(G_TPOOL_TXNS, &do_async_txnop, d, 0);
|
||||||
|
|
||||||
// Watch for events on the output pipe
|
|
||||||
// TODO: Can we do this just once ?!
|
|
||||||
driver_select(d->port, (ErlDrvEvent)(size_t)d->pipe_fds[0], DO_READ, 1);
|
|
||||||
|
|
||||||
// Outbuf is <<Rc:32>>
|
// Outbuf is <<Rc:32>>
|
||||||
RETURN_INT(0, outbuf);
|
RETURN_INT(0, outbuf);
|
||||||
|
@ -397,11 +408,7 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
case CMD_PUT:
|
case CMD_PUT:
|
||||||
case CMD_GET:
|
case CMD_GET:
|
||||||
{
|
{
|
||||||
// If another async op is pending, fail
|
FAIL_IF_ASYNC_PENDING(d, outbuf);
|
||||||
if (d->async_op != CMD_NONE)
|
|
||||||
{
|
|
||||||
RETURN_INT(ERROR_ASYNC_PENDING, outbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inbuf is: << DbRef:32, Rest/binary>>
|
// Inbuf is: << DbRef:32, Rest/binary>>
|
||||||
int dbref = UNPACK_INT(inbuf, 0);
|
int dbref = UNPACK_INT(inbuf, 0);
|
||||||
|
@ -411,19 +418,20 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
// the underlying handle disappearing since we have a reference.
|
// the underlying handle disappearing since we have a reference.
|
||||||
if (has_dbref(d, dbref))
|
if (has_dbref(d, dbref))
|
||||||
{
|
{
|
||||||
// Allocate operation structure
|
// If the working buffer is large enough, copy the data to put/get into it. Otherwise, realloc
|
||||||
void* thread_data = zalloc(sizeof(AsyncData) + inbuf_sz);
|
// until it is large enough
|
||||||
AsyncData* adata = (AsyncData*)thread_data;
|
if (d->work_buffer_sz < inbuf_sz)
|
||||||
adata->port = d;
|
{
|
||||||
adata->db = G_DATABASES[dbref].db;
|
d->work_buffer = driver_realloc(d->work_buffer, inbuf_sz);
|
||||||
adata->payload = thread_data + sizeof(AsyncData);
|
d->work_buffer_sz = inbuf_sz;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the payload into place
|
// Copy the payload into place
|
||||||
memcpy(adata->payload, inbuf, inbuf_sz);
|
memcpy(d->work_buffer, inbuf, inbuf_sz);
|
||||||
|
d->work_buffer_offset = inbuf_sz;
|
||||||
|
|
||||||
// Mark the port as busy and then schedule the appropriate async operation
|
// Mark the port as busy and then schedule the appropriate async operation
|
||||||
d->async_op = cmd;
|
d->async_op = cmd;
|
||||||
|
|
||||||
TPoolJobFunc fn;
|
TPoolJobFunc fn;
|
||||||
if (cmd == CMD_PUT)
|
if (cmd == CMD_PUT)
|
||||||
{
|
{
|
||||||
|
@ -433,14 +441,8 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
{
|
{
|
||||||
fn = &do_async_get;
|
fn = &do_async_get;
|
||||||
}
|
}
|
||||||
|
|
||||||
d->async_data = adata;
|
|
||||||
d->async_pool = G_TPOOL_GENERAL;
|
d->async_pool = G_TPOOL_GENERAL;
|
||||||
d->async_job = bdberl_tpool_run(G_TPOOL_GENERAL, fn, adata, 0);
|
d->async_job = bdberl_tpool_run(G_TPOOL_GENERAL, fn, d, 0);
|
||||||
|
|
||||||
// Watch for events on the output pipe
|
|
||||||
// TODO: Can we do this just once ?!
|
|
||||||
driver_select(d->port, (ErlDrvEvent)(size_t)d->pipe_fds[0], DO_READ, 1);
|
|
||||||
|
|
||||||
// Let caller know that the operation is in progress
|
// Let caller know that the operation is in progress
|
||||||
// Outbuf is: <<0:32>>
|
// Outbuf is: <<0:32>>
|
||||||
|
@ -471,100 +473,6 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void bdberl_drv_ready_input(ErlDrvData handle, ErlDrvEvent event)
|
|
||||||
{
|
|
||||||
PortData* d = (PortData*)handle;
|
|
||||||
// printf("%p: ready_input; cmd = %d; rc = %d\n", d->port, d->async_op,
|
|
||||||
// ((AsyncData*)d->async_data)->rc);
|
|
||||||
|
|
||||||
// Empty out the queue
|
|
||||||
int readbuf;
|
|
||||||
while (read((size_t)event, &readbuf, sizeof(readbuf)) > 0) { ; }
|
|
||||||
driver_select(d->port, event, DO_READ, 0);
|
|
||||||
|
|
||||||
// The async op has completed running on the thread pool -- process the results
|
|
||||||
switch (d->async_op)
|
|
||||||
{
|
|
||||||
case CMD_PUT:
|
|
||||||
case CMD_TXN_COMMIT:
|
|
||||||
case CMD_TXN_ABORT:
|
|
||||||
{
|
|
||||||
AsyncData* adata = (AsyncData*)d->async_data;
|
|
||||||
|
|
||||||
// Extract return code == if it's zero, send back "ok" to driver process; otherwise
|
|
||||||
// send a {error, Reason} tuple
|
|
||||||
if (adata->rc == 0)
|
|
||||||
{
|
|
||||||
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("ok") };
|
|
||||||
driver_output_term(d->port, response, sizeof(response) / sizeof(response[0]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("error"),
|
|
||||||
ERL_DRV_INT, adata->rc,
|
|
||||||
ERL_DRV_TUPLE, 2};
|
|
||||||
driver_output_term(d->port, response, sizeof(response) / sizeof(response[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this was a commit/abort, or a deadlock occurred while in a transaction,
|
|
||||||
// clear out the handle -- it's already invalid
|
|
||||||
if (d->async_op == CMD_TXN_COMMIT || d->async_op == CMD_TXN_ABORT ||
|
|
||||||
(d->txn && (adata->rc == DB_LOCK_NOTGRANTED || adata->rc == DB_LOCK_DEADLOCK)))
|
|
||||||
{
|
|
||||||
d->txn = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup async data and mark the port as not busy
|
|
||||||
driver_free(d->async_data);
|
|
||||||
d->async_data = 0;
|
|
||||||
d->async_op = CMD_NONE;
|
|
||||||
d->async_job = 0;
|
|
||||||
d->async_pool = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case CMD_GET:
|
|
||||||
{
|
|
||||||
// Extract return code == if it's zero, send back {ok, Payload} or not_found to driver
|
|
||||||
// process; otherwise send a {error, Reason} tuple
|
|
||||||
AsyncData* adata = (AsyncData*)d->async_data;
|
|
||||||
if (adata->rc == 0)
|
|
||||||
{
|
|
||||||
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("ok"),
|
|
||||||
ERL_DRV_BUF2BINARY, (ErlDrvTermData)adata->payload, (ErlDrvUInt)adata->payload_sz,
|
|
||||||
ERL_DRV_TUPLE, 2};
|
|
||||||
driver_output_term(d->port, response, sizeof(response) / sizeof(response[0]));
|
|
||||||
}
|
|
||||||
else if (adata->rc == DB_NOTFOUND)
|
|
||||||
{
|
|
||||||
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("not_found") };
|
|
||||||
driver_output_term(d->port, response, sizeof(response) / sizeof(response[0]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("error"),
|
|
||||||
ERL_DRV_INT, adata->rc,
|
|
||||||
ERL_DRV_TUPLE, 2};
|
|
||||||
driver_output_term(d->port, response, sizeof(response) / sizeof(response[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a deadlock occurred while in a transaction, clear out the handle -- it's
|
|
||||||
// already invalid
|
|
||||||
if (d->txn && (adata->rc == DB_LOCK_DEADLOCK || adata->rc == DB_LOCK_NOTGRANTED))
|
|
||||||
{
|
|
||||||
d->txn = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup async data and mark the port as not busy
|
|
||||||
driver_free(adata->payload);
|
|
||||||
driver_free(d->async_data);
|
|
||||||
d->async_data = 0;
|
|
||||||
d->async_op = CMD_NONE;
|
|
||||||
d->async_job = 0;
|
|
||||||
d->async_pool = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int open_database(const char* name, DBTYPE type, unsigned int flags, PortData* data, int* dbref_res)
|
static int open_database(const char* name, DBTYPE type, unsigned int flags, PortData* data, int* dbref_res)
|
||||||
{
|
{
|
||||||
|
@ -773,8 +681,12 @@ static void tune_system(int target, void* values, BinHelper* bh)
|
||||||
static void do_async_put(void* arg)
|
static void do_async_put(void* arg)
|
||||||
{
|
{
|
||||||
// Payload is: <<DbRef:32, Flags:32, KeyLen:32, Key:KeyLen, ValLen:32, Val:ValLen>>
|
// Payload is: <<DbRef:32, Flags:32, KeyLen:32, Key:KeyLen, ValLen:32, Val:ValLen>>
|
||||||
AsyncData* adata = (AsyncData*)arg;
|
PortData* d = (PortData*)arg;
|
||||||
unsigned flags = UNPACK_INT(adata->payload, 4);
|
|
||||||
|
// Get the database reference and flags from the payload
|
||||||
|
int dbref = UNPACK_INT(d->work_buffer, 0);
|
||||||
|
DB* db = G_DATABASES[dbref].db;
|
||||||
|
unsigned int flags = UNPACK_INT(d->work_buffer, 4);
|
||||||
|
|
||||||
// Setup DBTs
|
// Setup DBTs
|
||||||
DBT key;
|
DBT key;
|
||||||
|
@ -783,31 +695,55 @@ static void do_async_put(void* arg)
|
||||||
memset(&value, '\0', sizeof(DBT));
|
memset(&value, '\0', sizeof(DBT));
|
||||||
|
|
||||||
// Parse payload into DBTs
|
// Parse payload into DBTs
|
||||||
key.size = UNPACK_INT(adata->payload, 8);
|
key.size = UNPACK_INT(d->work_buffer, 8);
|
||||||
key.data = UNPACK_BLOB(adata->payload, 12);
|
key.data = UNPACK_BLOB(d->work_buffer, 12);
|
||||||
value.size = UNPACK_INT(adata->payload, 12 + key.size);
|
value.size = UNPACK_INT(d->work_buffer, 12 + key.size);
|
||||||
value.data = UNPACK_BLOB(adata->payload, 12 + key.size + 4);
|
value.data = UNPACK_BLOB(d->work_buffer, 12 + key.size + 4);
|
||||||
|
|
||||||
// Execute the actual put -- we'll process the result back in the driver_async_ready function
|
// Execute the actual put. All databases are opened with AUTO_COMMIT, so if msg->port->txn
|
||||||
// All databases are opened with AUTO_COMMIT, so if msg->port->txn is NULL, the put will still
|
// is NULL, the put will still be atomic
|
||||||
// be atomic
|
int rc = db->put(db, d->txn, &key, &value, flags);
|
||||||
adata->rc = adata->db->put(adata->db, adata->port->txn, &key, &value, flags);
|
|
||||||
|
|
||||||
// If any error occurs while we have a txn action, abort it
|
// If any error occurs while we have a txn action, abort it
|
||||||
if (adata->port->txn && adata->rc)
|
if (d->txn && rc)
|
||||||
{
|
{
|
||||||
adata->port->txn->abort(adata->port->txn);
|
d->txn->abort(d->txn);
|
||||||
|
d->txn = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue a signal for the port to know that the operation is complete
|
// Save the port and pid references -- we need copies independent from the PortData
|
||||||
signal_port(adata->port);
|
// structure. Once we release the port_lock after clearing the cmd, it's possible that
|
||||||
|
// the port could go away without waiting on us to finish. This is acceptable, but we need
|
||||||
|
// to be certain that there is no overlap of data between the two threads. driver_send_term
|
||||||
|
// is safe to use from a thread, even if the port you're sending from has already expired.
|
||||||
|
ErlDrvPort port = d->port;
|
||||||
|
ErlDrvTermData port_owner = d->port_owner;
|
||||||
|
|
||||||
|
// Release the port for another operation
|
||||||
|
d->async_pool = 0;
|
||||||
|
d->async_job = 0;
|
||||||
|
d->work_buffer_offset = 0;
|
||||||
|
erl_drv_mutex_lock(d->port_lock);
|
||||||
|
d->async_op = CMD_NONE;
|
||||||
|
erl_drv_mutex_unlock(d->port_lock);
|
||||||
|
|
||||||
|
// TODO: May need to tag the messages a bit more explicitly so that if another async
|
||||||
|
// job runs to completion before the message gets delivered we don't mis-interpret this
|
||||||
|
// response code.
|
||||||
|
send_ok_or_error(port, port_owner, rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_async_get(void* arg)
|
static void do_async_get(void* arg)
|
||||||
{
|
{
|
||||||
// Payload is: << DbRef:32, Flags:32, KeyLen:32, Key:KeyLen >>
|
// Payload is: << DbRef:32, Flags:32, KeyLen:32, Key:KeyLen >>
|
||||||
AsyncData* adata = (AsyncData*)arg;
|
PortData* d = (PortData*)arg;
|
||||||
unsigned flags = UNPACK_INT(adata->payload, 4);
|
|
||||||
|
// Get the database object, using the provided ref
|
||||||
|
int dbref = UNPACK_INT(d->work_buffer, 0);
|
||||||
|
DB* db = G_DATABASES[dbref].db;
|
||||||
|
|
||||||
|
// Extract operation flags
|
||||||
|
unsigned flags = UNPACK_INT(d->work_buffer, 4);
|
||||||
|
|
||||||
// Setup DBTs
|
// Setup DBTs
|
||||||
DBT key;
|
DBT key;
|
||||||
|
@ -816,54 +752,109 @@ static void do_async_get(void* arg)
|
||||||
memset(&value, '\0', sizeof(DBT));
|
memset(&value, '\0', sizeof(DBT));
|
||||||
|
|
||||||
// Parse payload into DBT
|
// Parse payload into DBT
|
||||||
key.size = UNPACK_INT(adata->payload, 8);
|
key.size = UNPACK_INT(d->work_buffer, 8);
|
||||||
key.data = UNPACK_BLOB(adata->payload, 12);
|
key.data = UNPACK_BLOB(d->work_buffer, 12);
|
||||||
|
|
||||||
// Allocate memory to hold the value -- hard code initial size to 4k
|
// Allocate a buffer for the output value
|
||||||
// TODO: Make this smarter!
|
value.data = driver_alloc(4096);
|
||||||
value.data = zalloc(4096);
|
|
||||||
value.ulen = 4096;
|
value.ulen = 4096;
|
||||||
value.flags = DB_DBT_USERMEM;
|
value.flags = DB_DBT_USERMEM;
|
||||||
|
|
||||||
int rc = adata->db->get(adata->db, adata->port->txn, &key, &value, flags);
|
int rc = db->get(db, d->txn, &key, &value, flags);
|
||||||
while (rc == DB_BUFFER_SMALL)
|
while (rc == DB_BUFFER_SMALL)
|
||||||
{
|
{
|
||||||
// Grow our value buffer and try again
|
// Grow our value buffer and try again
|
||||||
value.data = driver_realloc(value.data, value.size);
|
value.data = driver_realloc(value.data, value.size);
|
||||||
value.ulen = value.size;
|
value.ulen = value.size;
|
||||||
rc = adata->db->get(adata->db, adata->port->txn, &key, &value, flags);
|
rc = db->get(db, d->txn, &key, &value, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
adata->payload = value.data;
|
// Cleanup transaction as necessary
|
||||||
adata->payload_sz = value.size; // Not ulen -- we want the actual data size
|
if (rc && rc != DB_NOTFOUND && d->txn)
|
||||||
adata->rc = rc;
|
|
||||||
|
|
||||||
// If any error occurs while we have a txn action, abort it
|
|
||||||
if (adata->rc != DB_NOTFOUND && adata->port->txn && adata->rc)
|
|
||||||
{
|
{
|
||||||
adata->port->txn->abort(adata->port->txn);
|
d->txn->abort(d->txn);
|
||||||
|
d->txn = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
signal_port(adata->port);
|
// Save the port and pid references -- we need copies independent from the PortData
|
||||||
|
// structure. Once we release the port_lock after clearing the cmd, it's possible that
|
||||||
|
// the port could go away without waiting on us to finish. This is acceptable, but we need
|
||||||
|
// to be certain that there is no overlap of data between the two threads. driver_send_term
|
||||||
|
// is safe to use from a thread, even if the port you're sending from has already expired.
|
||||||
|
ErlDrvPort port = d->port;
|
||||||
|
ErlDrvTermData port_owner = d->port_owner;
|
||||||
|
|
||||||
|
// Release the port for another operation
|
||||||
|
d->async_pool = 0;
|
||||||
|
d->async_job = 0;
|
||||||
|
d->work_buffer_offset = 0;
|
||||||
|
erl_drv_mutex_lock(d->port_lock);
|
||||||
|
d->async_op = CMD_NONE;
|
||||||
|
erl_drv_mutex_unlock(d->port_lock);
|
||||||
|
|
||||||
|
// Notify port of result
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("ok"),
|
||||||
|
ERL_DRV_BUF2BINARY, (ErlDrvTermData)value.data, (ErlDrvUInt)value.size,
|
||||||
|
ERL_DRV_TUPLE, 2};
|
||||||
|
driver_send_term(port, port_owner, response, sizeof(response) / sizeof(response[0]));
|
||||||
|
}
|
||||||
|
else if (rc == DB_NOTFOUND)
|
||||||
|
{
|
||||||
|
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("not_found") };
|
||||||
|
driver_send_term(port, port_owner, response, sizeof(response) / sizeof(response[0]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("error"),
|
||||||
|
ERL_DRV_INT, rc,
|
||||||
|
ERL_DRV_TUPLE, 2};
|
||||||
|
driver_send_term(port, port_owner, response, sizeof(response) / sizeof(response[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, clean up value buffer (driver_send_term made a copy)
|
||||||
|
driver_free(value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void do_async_txnop(void* arg)
|
static void do_async_txnop(void* arg)
|
||||||
{
|
{
|
||||||
AsyncData* adata = (AsyncData*)arg;
|
PortData* d = (PortData*)arg;
|
||||||
// printf("%p: do_async_txnop\n", adata->port->port);
|
|
||||||
|
|
||||||
// Execute the actual commit/abort
|
// Execute the actual commit/abort
|
||||||
if (adata->port->async_op == CMD_TXN_COMMIT)
|
int rc = 0;
|
||||||
|
if (d->async_op == CMD_TXN_COMMIT)
|
||||||
{
|
{
|
||||||
unsigned flags = (unsigned)(size_t)adata->payload;
|
rc = d->txn->commit(d->txn, d->async_flags);
|
||||||
adata->rc = adata->port->txn->commit(adata->port->txn, flags);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
adata->rc = adata->port->txn->abort(adata->port->txn);
|
rc = d->txn->abort(d->txn);
|
||||||
}
|
}
|
||||||
|
|
||||||
signal_port(adata->port);
|
// The transaction is now invalid, regardless of the outcome.
|
||||||
|
d->txn = 0;
|
||||||
|
|
||||||
|
// Save the port and pid references -- we need copies independent from the PortData
|
||||||
|
// structure. Once we release the port_lock after clearing the cmd, it's possible that
|
||||||
|
// the port could go away without waiting on us to finish. This is acceptable, but we need
|
||||||
|
// to be certain that there is no overlap of data between the two threads. driver_send_term
|
||||||
|
// is safe to use from a thread, even if the port you're sending from has already expired.
|
||||||
|
ErlDrvPort port = d->port;
|
||||||
|
ErlDrvTermData port_owner = d->port_owner;
|
||||||
|
|
||||||
|
// Release the port for another operation
|
||||||
|
d->async_pool = 0;
|
||||||
|
d->async_job = 0;
|
||||||
|
d->work_buffer_offset = 0;
|
||||||
|
erl_drv_mutex_lock(d->port_lock);
|
||||||
|
d->async_op = CMD_NONE;
|
||||||
|
erl_drv_mutex_unlock(d->port_lock);
|
||||||
|
|
||||||
|
// TODO: May need to tag the messages a bit more explicitly so that if another async
|
||||||
|
// job runs to completion before the message gets delivered we don't mis-interpret this
|
||||||
|
// response code.
|
||||||
|
send_ok_or_error(port, port_owner, rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1047,11 +1038,6 @@ static int alloc_dbref()
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void signal_port(PortData* d)
|
|
||||||
{
|
|
||||||
int flag = 1;
|
|
||||||
write(d->pipe_fds[1], &flag, sizeof(flag));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread function that runs the deadlock checker periodically
|
* Thread function that runs the deadlock checker periodically
|
||||||
|
@ -1105,3 +1091,19 @@ static void* trickle_write(void* arg)
|
||||||
printf("Trickle writer exiting.\n");
|
printf("Trickle writer exiting.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void send_ok_or_error(ErlDrvPort port, ErlDrvTermData pid, int rc)
|
||||||
|
{
|
||||||
|
if (rc == 0)
|
||||||
|
{
|
||||||
|
ErlDrvTermData response[] = {ERL_DRV_ATOM, driver_mk_atom("ok")};
|
||||||
|
driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErlDrvTermData response[] = { ERL_DRV_ATOM, driver_mk_atom("error"),
|
||||||
|
ERL_DRV_INT, rc,
|
||||||
|
ERL_DRV_TUPLE, 2};
|
||||||
|
driver_send_term(port, pid, response, sizeof(response) / sizeof(response[0]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,8 +24,6 @@ static int bdberl_drv_control(ErlDrvData handle, unsigned int cmd,
|
||||||
char* inbuf, int inbuf_sz,
|
char* inbuf, int inbuf_sz,
|
||||||
char** outbuf, int outbuf_sz);
|
char** outbuf, int outbuf_sz);
|
||||||
|
|
||||||
static void bdberl_drv_ready_input(ErlDrvData handle, ErlDrvEvent ev);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command codes
|
* Command codes
|
||||||
|
@ -85,7 +83,7 @@ ErlDrvEntry bdberl_drv_entry =
|
||||||
bdberl_drv_start, /* L_PTR start, called when port is opened */
|
bdberl_drv_start, /* L_PTR start, called when port is opened */
|
||||||
bdberl_drv_stop, /* F_PTR stop, called when port is closed */
|
bdberl_drv_stop, /* F_PTR stop, called when port is closed */
|
||||||
NULL, /* F_PTR output, called when erlang has sent */
|
NULL, /* F_PTR output, called when erlang has sent */
|
||||||
bdberl_drv_ready_input, /* F_PTR ready_input, called when input descriptor ready */
|
NULL, /* F_PTR ready_input, called when input descriptor ready */
|
||||||
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
NULL, /* F_PTR ready_output, called when output descriptor ready */
|
||||||
"bdberl_drv", /* driver_name */
|
"bdberl_drv", /* driver_name */
|
||||||
bdberl_drv_finish, /* F_PTR finish, called when unloaded */
|
bdberl_drv_finish, /* F_PTR finish, called when unloaded */
|
||||||
|
@ -134,33 +132,30 @@ typedef struct
|
||||||
{
|
{
|
||||||
ErlDrvPort port;
|
ErlDrvPort port;
|
||||||
|
|
||||||
|
ErlDrvMutex* port_lock; /* Mutex for this port (to permit async jobs to safely update this
|
||||||
|
* structure) */
|
||||||
|
|
||||||
|
ErlDrvTermData port_owner; /* Pid of the port owner */
|
||||||
|
|
||||||
DbRefList* dbrefs; /* List of databases that this port has opened */
|
DbRefList* dbrefs; /* List of databases that this port has opened */
|
||||||
|
|
||||||
DB_TXN* txn; /* Transaction handle for this port; each port may only have 1 txn
|
DB_TXN* txn; /* Transaction handle for this port; each port may only have 1 txn
|
||||||
* active */
|
* active */
|
||||||
|
|
||||||
int pipe_fds[2]; /* Array of pipe fds for signaling purposes */
|
|
||||||
|
|
||||||
int async_op; /* Value indicating what async op is pending */
|
int async_op; /* Value indicating what async op is pending */
|
||||||
|
|
||||||
void* async_data; /* Opaque point to data used during async op */
|
int async_flags; /* Flags for the async op command */
|
||||||
|
|
||||||
TPoolJob* async_job; /* Active job on the thread pool */
|
TPoolJob* async_job; /* Active job on the thread pool */
|
||||||
|
|
||||||
TPool* async_pool; /* Pool the async job is running on */
|
TPool* async_pool; /* Pool the async job is running on */
|
||||||
|
|
||||||
|
void* work_buffer;
|
||||||
|
|
||||||
|
unsigned int work_buffer_sz;
|
||||||
|
|
||||||
|
unsigned int work_buffer_offset;
|
||||||
|
|
||||||
} PortData;
|
} PortData;
|
||||||
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
PortData* port; /* Port that originated this request -- READ ONLY! */
|
|
||||||
int rc; /* Return code from operation */
|
|
||||||
DB* db; /* Database to use for data storage/retrieval */
|
|
||||||
void* payload; /* Packed key/value data */
|
|
||||||
int payload_sz; /* Size of payload */
|
|
||||||
} AsyncData;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue