Packages
rocksdb
0.6.2
3.1.1
3.1.0
3.0.0
2.6.2
2.6.1
retired
2.6.0
retired
2.5.0
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
1.9.0
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.1
1.1.0
1.0.0
0.26.2
0.26.1
0.26.0
0.25.0
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.0
0.21.0
0.20.1
0.20.0
0.19.0
0.18.0
0.17.0
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.1
0.7.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
RocksDB for Erlang
Current section
Files
Jump to
Current section
Files
c_src/threading.cc
// -------------------------------------------------------------------
//
// eleveldb: Erlang Wrapper for LevelDB (http://code.google.com/p/leveldb/)
//
// Copyright (c) 2011-2013 Basho Technologies, Inc. All Rights Reserved.
//
// This file is provided to you under the Apache License,
// Version 2.0 (the "License"); you may not use this file
// except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// -------------------------------------------------------------------
#include <sstream>
#include <stdexcept>
#ifndef INCL_THREADING_H
#include "threading.h"
#endif
#ifndef INCL_WORKITEMS_H
#include "workitems.h"
#endif
#ifndef __EROCKSDB_DETAIL_HPP
#include "detail.hpp"
#endif
namespace erocksdb {
void *erocksdb_write_thread_worker(void *args);
/**
* Meta / managment data related to a worker thread.
*/
struct ThreadData
{
ErlNifTid * m_ErlTid; //!< erlang handle for this thread
volatile uint32_t m_Available; //!< 1 if thread waiting, using standard type for atomic operation
class erocksdb_thread_pool & m_Pool; //!< parent pool object
volatile erocksdb::WorkTask * m_DirectWork; //!< work passed direct to thread
pthread_mutex_t m_Mutex; //!< mutex for condition variable
pthread_cond_t m_Condition; //!< condition for thread waiting
ThreadData(class erocksdb_thread_pool & Pool)
: m_ErlTid(NULL), m_Available(0), m_Pool(Pool), m_DirectWork(NULL)
{
pthread_mutex_init(&m_Mutex, NULL);
pthread_cond_init(&m_Condition, NULL);
return;
} // ThreadData
private:
ThreadData();
}; // class ThreadData
bool // returns true if available worker thread found and claimed
erocksdb_thread_pool::FindWaitingThread(
erocksdb::WorkTask * work) // non-NULL to pass current work directly to a thread,
// NULL to potentially nudge an available worker toward backlog queue
{
bool ret_flag;
size_t start, index, pool_size;
ret_flag=false;
// pick "random" place in thread list. hopefully
// list size is prime number.
pool_size=threads.size();
start=(size_t)pthread_self() % pool_size;
index=start;
do
{
// perform quick test to see thread available
if (0!=threads[index]->m_Available)
{
// perform expensive compare and swap to potentially
// claim worker thread (this is an exclusive claim to the worker)
ret_flag = erocksdb::compare_and_swap(&threads[index]->m_Available, 1, 0);
// the compare/swap only succeeds if worker thread is sitting on
// pthread_cond_wait ... or is about to be there but is holding
// the mutex already
if (ret_flag)
{
// man page says mutex lock optional, experience in
// this code says it is not. using broadcast instead
// of signal to cover one other race condition
// that should never happen with single thread waiting.
pthread_mutex_lock(&threads[index]->m_Mutex);
threads[index]->m_DirectWork=work;
pthread_cond_broadcast(&threads[index]->m_Condition);
pthread_mutex_unlock(&threads[index]->m_Mutex);
} // if
} // if
index=(index+1)%pool_size;
} while(index!=start && !ret_flag);
return(ret_flag);
} // FindWaitingThread
bool erocksdb_thread_pool::submit(erocksdb::WorkTask* item)
{
bool ret_flag(false);
if (NULL!=item)
{
item->RefInc();
if(shutdown_pending())
{
item->RefDec();
ret_flag=false;
} // if
// try to give work to a waiting thread first
else if (!FindWaitingThread(item))
{
// no waiting threads, put on backlog queue
lock();
erocksdb::inc_and_fetch(&work_queue_atomic);
work_queue.push_back(item);
unlock();
// to address race condition, thread might be waiting now
FindWaitingThread(NULL);
ret_flag=true;
} // if
else
{
ret_flag=true;
} // else
} // if
return(ret_flag);
} // submit
// not clear that this works or is testable
bool erocksdb_thread_pool::resize_thread_pool(const size_t n)
{
erocksdb::MutexLock l(thread_resize_pool_mutex);
if(0 == n)
return false;
if(threads.size() == n)
return true; // nothing to do
// Strictly expanding is less expensive:
if(threads.size() < n)
return grow_thread_pool(n - threads.size());
if(false == drain_thread_pool())
return false;
return grow_thread_pool(n);
}
erocksdb_thread_pool::erocksdb_thread_pool(const size_t thread_pool_size)
: work_queue_pending(0), work_queue_lock(0),
work_queue_atomic(0),
shutdown(false)
{
work_queue_pending = enif_cond_create(const_cast<char *>("work_queue_pending"));
if(0 == work_queue_pending)
throw std::runtime_error("cannot create condition work_queue_pending");
work_queue_lock = enif_mutex_create(const_cast<char *>("work_queue_lock"));
if(0 == work_queue_lock)
throw std::runtime_error("cannot create work_queue_lock");
if(false == grow_thread_pool(thread_pool_size))
throw std::runtime_error("cannot resize thread pool");
}
erocksdb_thread_pool::~erocksdb_thread_pool()
{
drain_thread_pool(); // all kids out of the pool
enif_mutex_destroy(work_queue_lock);
enif_cond_destroy(work_queue_pending);
}
// Grow the thread pool by nthreads threads:
// may not work at this time ...
bool erocksdb_thread_pool::grow_thread_pool(const size_t nthreads)
{
erocksdb::MutexLock l(threads_lock);
ThreadData * new_thread;
if(0 >= nthreads)
return true; // nothing to do, but also not failure
if(N_THREADS_MAX < nthreads + threads.size())
return false;
// At least one thread means that we don't shut threads down:
shutdown = false;
threads.reserve(nthreads);
for(size_t i = nthreads; i; --i)
{
std::ostringstream thread_name;
thread_name << "erocksdb_write_thread_" << threads.size() + 1;
ErlNifTid *thread_id = static_cast<ErlNifTid *>(enif_alloc(sizeof(ErlNifTid)));
if(0 == thread_id)
return false;
new_thread=new ThreadData(*this);
const int result = enif_thread_create(const_cast<char *>(thread_name.str().c_str()), thread_id,
erocksdb_write_thread_worker,
static_cast<void *>(new_thread),
0);
new_thread->m_ErlTid=thread_id;
if(0 != result)
return false;
threads.push_back(new_thread);
}
return true;
}
// Shut down and destroy all threads in the thread pool:
// does not currently work
bool erocksdb_thread_pool::drain_thread_pool()
{
struct release_thread
{
bool state;
release_thread()
: state(true)
{}
void operator()(ErlNifTid*& tid)
{
if(0 != enif_thread_join(*tid, 0))
state = false;
enif_free(tid);
}
bool operator()() const { return state; }
} rt;
// Signal shutdown and raise all threads:
shutdown = true;
enif_cond_broadcast(work_queue_pending);
erocksdb::MutexLock l(threads_lock);
#if 0
while(!threads.empty())
{
// Rebroadcast on each invocation (workers might not see the signal otherwise):
enif_cond_broadcast(work_queue_pending);
rt(threads.top());
threads.pop();
}
#endif
return rt();
}
bool erocksdb_thread_pool::notify_caller(erocksdb::WorkTask& work_item)
{
ErlNifPid pid;
bool ret_flag(true);
// Call the work function:
leofs::async_nif::work_result result = work_item();
if (result.is_set())
{
if(0 != enif_get_local_pid(work_item.local_env(), work_item.pid(), &pid))
{
/* Assemble a notification of the following form:
{ PID CallerHandle, ERL_NIF_TERM result } */
ERL_NIF_TERM result_tuple = enif_make_tuple2(work_item.local_env(),
work_item.caller_ref(), result.result());
ret_flag=(0 != enif_send(0, &pid, work_item.local_env(), result_tuple));
} // if
else
{
ret_flag=false;
} // else
} // if
return(ret_flag);
}
/**
* Worker threads: worker threads have 3 states:
* A. doing nothing, available to be claimed: m_Available=1
* B. processing work passed by Erlang thread: m_Available=0, m_DirectWork=<non-null>
* C. processing backlog queue of work: m_Available=0, m_DirectWork=NULL
*/
void *erocksdb_write_thread_worker(void *args)
{
ThreadData &tdata = *(ThreadData *)args;
erocksdb_thread_pool& h = tdata.m_Pool;
erocksdb::WorkTask * submission;
submission=NULL;
while(!h.shutdown)
{
// is work assigned yet?
// check backlog work queue if not
if (NULL==submission)
{
// test non-blocking size for hint (much faster)
if (0!=h.work_queue_atomic)
{
// retest with locking
h.lock();
if (!h.work_queue.empty())
{
submission=h.work_queue.front();
h.work_queue.pop_front();
erocksdb::dec_and_fetch(&h.work_queue_atomic);
} // if
h.unlock();
} // if
} // if
// a work item identified (direct or queue), work it!
// then loop to test queue again
if (NULL!=submission)
{
erocksdb_thread_pool::notify_caller(*submission);
if (submission->resubmit())
{
submission->recycle();
h.submit(submission);
} // if
// resubmit will increment reference again, so
// always dec even in reuse case
submission->RefDec();
submission=NULL;
} // if
// no work found, attempt to go into wait state
// (but retest queue before sleep due to race condition)
else
{
pthread_mutex_lock(&tdata.m_Mutex);
tdata.m_DirectWork=NULL; // safety
// only wait if we are really sure no work pending
if (0==h.work_queue_atomic)
{
// yes, thread going to wait. set available now.
tdata.m_Available=1;
pthread_cond_wait(&tdata.m_Condition, &tdata.m_Mutex);
} // if
tdata.m_Available=0; // safety
submission=(erocksdb::WorkTask *)tdata.m_DirectWork; // NULL is valid
tdata.m_DirectWork=NULL;// safety
pthread_mutex_unlock(&tdata.m_Mutex);
} // else
} // while
return 0;
} // erocksdb_write_thread_worker
}; // namespace erocksdb