Merge branch 'async-nif'
diff --git a/LICENSE b/LICENSE
index 8c982e3..f479304 100644
--- a/LICENSE
+++ b/LICENSE
@@ -82,3 +82,35 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+
+The asynchronous queue code (c_src/async_queue.c and
+c_src/async_queue.h) is from the esnappy project, copyright 2011
+Konstantin V. Sorokin. It is subject to the following license:
+
+Copyright (c) 2011 Konstantin V. Sorokin
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/README.rst b/README.rst
index 2c8693d..4b84bfe 100644
--- a/README.rst
+++ b/README.rst
@@ -48,10 +48,10 @@
``mechanism``
Specifies whether to use the NIF implementation (``'nif'``) or a
- pool of port programs (``'port'``). Defaults to ``'port'``.
+ pool of port programs (``'port'``). Defaults to ``'nif'``.
- `WARNING: the NIF implementation will block Erlang VM scheduler
- threads and is not suitable for many applications.`
+ `Note: the NIF implementation no longer blocks the Erlang VM
+ scheduler threads`
``pool_size``
Specifies the size of the port program pool. Defaults to ``4``.
diff --git a/c_src/async_queue.c b/c_src/async_queue.c
new file mode 100644
index 0000000..e289c3a
--- /dev/null
+++ b/c_src/async_queue.c
@@ -0,0 +1,141 @@
+/*
+ From https://github.com/thekvs/esnappy:
+ Copyright (c) 2011 Konstantin V. Sorokin
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+// vim: shiftwidth=4 expandtab
+#include "async_queue.h"
+
+async_queue_t*
+async_queue_create()
+{
+ async_queue_t *aq;
+
+ aq = ALLOC(sizeof(*aq));
+
+ if (!aq) {
+ errx(1, "enif_alloc() failed");
+ }
+
+ aq->q = ALLOC(sizeof(*(aq->q)));
+
+ if (!(aq->q)) {
+ errx(1, "enif_alloc() failed");
+ }
+
+ TAILQ_INIT(aq->q);
+
+ aq->waiting_threads = aq->len = 0;
+
+ aq->mutex = enif_mutex_create("erlang_snappy_mutex");
+
+ if (!aq->mutex) {
+ errx(1, "enif_mutex_create() failed");
+ }
+
+ aq->cond = enif_cond_create("erlang_snappy_condvar");
+
+ if (!aq->cond) {
+ errx(1, "enif_cond_create() failed");
+ }
+
+ return aq;
+}
+
+int
+async_queue_length(async_queue_t *aq)
+{
+ int length;
+
+ MUTEX_LOCK(aq->mutex);
+ length = aq->len - aq->waiting_threads;
+ MUTEX_UNLOCK(aq->mutex);
+
+ return length;
+}
+
+void *
+async_queue_pop(async_queue_t *aq)
+{
+ struct async_queue_entry *en;
+ void *d;
+
+ MUTEX_LOCK(aq->mutex);
+
+ d = NULL;
+ aq->waiting_threads++;
+ while (TAILQ_EMPTY(aq->q)) {
+ enif_cond_wait(aq->cond, aq->mutex);
+ }
+ aq->waiting_threads--;
+
+ en = TAILQ_FIRST(aq->q);
+ TAILQ_REMOVE(aq->q, en, entries);
+ d = en->data;
+ aq->len--;
+ enif_free(en);
+
+ MUTEX_UNLOCK(aq->mutex);
+
+ return d;
+}
+
+void
+async_queue_push(async_queue_t *aq, void *data)
+{
+ struct async_queue_entry *en;
+
+ MUTEX_LOCK(aq->mutex);
+
+ en = ALLOC(sizeof(*en));
+ en->data = data;
+ TAILQ_INSERT_TAIL(aq->q, en, entries);
+ aq->len++;
+
+ COND_SIGNAL(aq->cond);
+ MUTEX_UNLOCK(aq->mutex);
+}
+
+void
+async_queue_destroy(async_queue_t *aq)
+{
+ struct async_queue_entry *en;
+
+ while (!TAILQ_EMPTY(aq->q)) {
+ en = TAILQ_FIRST(aq->q);
+ TAILQ_REMOVE(aq->q, en, entries);
+ enif_free(en);
+ }
+
+ COND_DESTROY(aq->cond);
+ MUTEX_DESTROY(aq->mutex);
+
+ enif_free(aq->q);
+ enif_free(aq);
+}
+
diff --git a/c_src/async_queue.h b/c_src/async_queue.h
new file mode 100644
index 0000000..48384d0
--- /dev/null
+++ b/c_src/async_queue.h
@@ -0,0 +1,83 @@
+/*
+ From https://github.com/thekvs/esnappy:
+ Copyright (c) 2011 Konstantin V. Sorokin
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+// vim: shiftwidth=4 expandtab
+#ifndef __ASYNC_QUEUE_H_INCLUDED__
+#define __ASYNC_QUEUE_H_INCLUDED__
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+
+#include <erl_nif.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(queue, async_queue_entry);
+
+struct async_queue_entry {
+ TAILQ_ENTRY(async_queue_entry) entries;
+ void *data;
+};
+
+typedef struct __async_queue {
+ struct queue *q;
+ ErlNifMutex *mutex;
+ ErlNifCond *cond;
+ int waiting_threads;
+ int len;
+} async_queue_t;
+
+async_queue_t* async_queue_create();
+int async_queue_length(async_queue_t *aq);
+void* async_queue_pop(async_queue_t *aq);
+void async_queue_push(async_queue_t *aq, void *data);
+void async_queue_destroy(async_queue_t *aq);
+
+#define ALLOC(size) enif_alloc(size)
+#define MUTEX_LOCK(mutex) enif_mutex_lock(mutex)
+#define MUTEX_UNLOCK(mutex) enif_mutex_unlock(mutex)
+#define MUTEX_DESTROY(mutex) enif_mutex_destroy(mutex)
+#define COND_SIGNAL(condvar) enif_cond_signal(condvar)
+#define COND_DESTROY(condvar) enif_cond_destroy(condvar)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif
diff --git a/c_src/bcrypt_nif.c b/c_src/bcrypt_nif.c
index 5dcf564..4186ac7 100644
--- a/c_src/bcrypt_nif.c
+++ b/c_src/bcrypt_nif.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>
+ * Copyright (c) 2011-2012 Hunter Morris <hunter.morris@smarkets.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -14,6 +14,7 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -21,13 +22,115 @@
#include "erl_nif.h"
#include "erl_blf.h"
+#include "bcrypt_nif.h"
-typedef unsigned char byte;
+void free_task(task_t* task)
+{
+ if (task->env != NULL)
+ enif_free_env(task->env);
+ enif_free(task);
+}
-char *bcrypt(const char *, const char *);
-void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
+task_t* alloc_task(task_type_t type)
+{
+ task_t* task = (task_t*)enif_alloc(sizeof(task_t));
+ if (task == NULL)
+ return NULL;
+ (void)memset(task, 0, sizeof(task_t));
+ task->type = type;
+ return task;
+}
-static ERL_NIF_TERM erl_encode_salt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, int num_orig_terms, const ERL_NIF_TERM orig_terms[])
+{
+ task_t* task = alloc_task(type);
+ task->pid = pid;
+ task->env = enif_alloc_env();
+ if (task->env == NULL) {
+ free_task(task);
+ return NULL;
+ }
+
+ if (type == HASH) {
+ assert(num_orig_terms == 2);
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[0]),
+ &task->data.hash.salt)) {
+ free_task(task);
+ return NULL;
+ }
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[1]),
+ &task->data.hash.password)) {
+ free_task(task);
+ return NULL;
+ }
+ }
+
+ task->ref = enif_make_copy(task->env, ref);
+ return task;
+}
+
+static ERL_NIF_TERM hashpw(task_t* task)
+{
+ char password[1024] = { 0 };
+ char salt[1024] = { 0 };
+ char *ret = NULL;
+
+ size_t password_sz = 1024;
+ if (password_sz > task->data.hash.password.size)
+ password_sz = task->data.hash.password.size;
+ (void)memcpy(&password, task->data.hash.password.data, password_sz);
+
+ size_t salt_sz = 1024;
+ if (salt_sz > task->data.hash.salt.size)
+ salt_sz = task->data.hash.salt.size;
+ (void)memcpy(&salt, task->data.hash.salt.data, salt_sz);
+
+ if (NULL == (ret = bcrypt(password, salt)) || 0 == strcmp(ret, ":")) {
+ return enif_make_tuple3(
+ task->env,
+ enif_make_atom(task->env, "error"),
+ task->ref,
+ enif_make_string(task->env, "bcrypt failed", ERL_NIF_LATIN1));
+ }
+
+ return enif_make_tuple3(
+ task->env,
+ enif_make_atom(task->env, "ok"),
+ task->ref,
+ enif_make_string(task->env, ret, ERL_NIF_LATIN1));
+}
+
+void* async_worker(void* arg)
+{
+ ctx_t* ctx;
+ task_t* task;
+
+ ERL_NIF_TERM result;
+
+ ctx = (ctx_t*)arg;
+
+ while (1) {
+ task = (task_t*)async_queue_pop(ctx->queue);
+
+ if (task->type == SHUTDOWN) {
+ free_task(task);
+ break;
+ } else if (task->type == HASH) {
+ result = hashpw(task);
+ } else {
+ errx(1, "Unexpected task type: %i", task->type);
+ }
+
+ enif_send(NULL, &task->pid, task->env, result);
+ free_task(task);
+ }
+
+ return NULL;
+}
+
+static ERL_NIF_TERM bcrypt_encode_salt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary csalt, bin;
unsigned long log_rounds;
@@ -52,32 +155,87 @@
return enif_make_string(env, (char *)bin.data, ERL_NIF_LATIN1);
}
-static ERL_NIF_TERM hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+static ERL_NIF_TERM bcrypt_hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
- char pw[1024];
- char salt[1024];
- char *ret = NULL;
+ ctx_t *ctx;
+ task_t *task;
+ ErlNifPid pid;
- (void)memset(&pw, '\0', sizeof(pw));
- (void)memset(&salt, '\0', sizeof(salt));
-
- if (enif_get_string(env, argv[0], pw, sizeof(pw), ERL_NIF_LATIN1) < 1)
+ if (argc != 5)
return enif_make_badarg(env);
- if (enif_get_string(env, argv[1], salt, sizeof(salt), ERL_NIF_LATIN1) < 1)
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);
+
+ if (!enif_get_resource(env, argv[0], priv->bcrypt_rt, (void**)(&ctx)))
return enif_make_badarg(env);
- if (NULL == (ret = bcrypt(pw, salt)) || 0 == strcmp(ret, ":")) {
+ if (!enif_is_ref(env, argv[1]))
+ return enif_make_badarg(env);
+
+ if (!enif_get_local_pid(env, argv[2], &pid))
+ return enif_make_badarg(env);
+
+ ERL_NIF_TERM orig_terms[] = { argv[4], argv[3] };
+ task = alloc_init_task(HASH, argv[1], pid, 2, orig_terms);
+
+ if (!task)
+ return enif_make_badarg(env);
+
+ async_queue_push(ctx->queue, task);
+
+ return enif_make_atom(env, "ok");
+}
+
+static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM ret;
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);
+ ctx_t* ctx = (ctx_t*)enif_alloc_resource(priv->bcrypt_rt, sizeof(ctx_t));
+ if (ctx == NULL)
+ return enif_make_badarg(env);
+ ctx->queue = async_queue_create();
+ ctx->topts = enif_thread_opts_create("bcrypt_thread_opts");
+ if (enif_thread_create("bcrypt_worker", &ctx->tid, async_worker, ctx, ctx->topts) != 0) {
+ enif_release_resource(ctx);
return enif_make_badarg(env);
}
-
- return enif_make_string(env, ret, ERL_NIF_LATIN1);
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
}
static ErlNifFunc bcrypt_nif_funcs[] =
{
- {"encode_salt", 2, erl_encode_salt},
- {"hashpw", 2, hashpw}
+ {"encode_salt", 2, bcrypt_encode_salt},
+ {"hashpw", 5, bcrypt_hashpw},
+ {"create_ctx", 0, bcrypt_create_ctx},
};
-ERL_NIF_INIT(bcrypt_nif, bcrypt_nif_funcs, NULL, NULL, NULL, NULL)
+static void bcrypt_rt_dtor(ErlNifEnv* env, void* obj)
+{
+ ctx_t *ctx = (ctx_t*)obj;
+ task_t *task = alloc_task(SHUTDOWN);
+ void *result = NULL;
+
+ async_queue_push(ctx->queue, task);
+ enif_thread_join(ctx->tid, &result);
+ async_queue_destroy(ctx->queue);
+ enif_thread_opts_destroy(ctx->topts);
+}
+
+static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ const char *mod = "bcrypt_nif";
+ const char *name = "nif_resource";
+
+ ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
+
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_alloc(sizeof(bcrypt_privdata_t));
+ priv->bcrypt_rt = enif_open_resource_type(env, mod, name, bcrypt_rt_dtor, flags, NULL);
+ if (priv->bcrypt_rt == NULL)
+ return -1;
+ *priv_data = priv;
+ return 0;
+}
+
+ERL_NIF_INIT(bcrypt_nif, bcrypt_nif_funcs, &on_load, NULL, NULL, NULL)
diff --git a/c_src/bcrypt_nif.h b/c_src/bcrypt_nif.h
new file mode 100644
index 0000000..5d513cb
--- /dev/null
+++ b/c_src/bcrypt_nif.h
@@ -0,0 +1,40 @@
+#ifndef ERLANG_BCRYPT_BCRYPT_NIF_H
+#define ERLANG_BCRYPT_BCRYPT_NIF_H
+
+#include "async_queue.h"
+
+typedef unsigned char byte;
+
+char *bcrypt(const char *, const char *);
+void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
+
+typedef struct {
+ ErlNifResourceType *bcrypt_rt;
+} bcrypt_privdata_t;
+
+typedef struct {
+ async_queue_t *queue;
+ ErlNifThreadOpts *topts;
+ ErlNifTid tid;
+} ctx_t;
+
+typedef enum {
+ UNKNOWN,
+ SHUTDOWN,
+ HASH
+} task_type_t;
+
+typedef struct {
+ task_type_t type;
+ ErlNifEnv *env;
+ ErlNifPid pid;
+ ERL_NIF_TERM ref;
+ union {
+ struct {
+ ErlNifBinary salt;
+ ErlNifBinary password;
+ } hash;
+ } data;
+} task_t;
+
+#endif // ERLANG_BCRYPT_BCRYPT_NIF_H
diff --git a/rebar.config b/rebar.config
index 3821a00..2d7e862 100644
--- a/rebar.config
+++ b/rebar.config
@@ -3,7 +3,7 @@
{so_specs,
[{"priv/bcrypt_nif.so",
- ["c_src/blowfish.o", "c_src/bcrypt.o", "c_src/bcrypt_nif.o"]}]}.
+ ["c_src/blowfish.o", "c_src/bcrypt.o", "c_src/bcrypt_nif.o", "c_src/async_queue.o"]}]}.
{post_hooks,
[{clean, "rm -f priv/bcrypt"},
diff --git a/src/bcrypt.app.src b/src/bcrypt.app.src
index 8242cb5..bf99118 100644
--- a/src/bcrypt.app.src
+++ b/src/bcrypt.app.src
@@ -1,7 +1,7 @@
%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
{application, bcrypt,
[{description, "An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt."},
- {vsn, "0.4.1"},
+ {vsn, "0.5.0"},
{registered, [bcrypt_sup, bcrypt_nif_worker, bcrypt_port_sup, bcrypt_pool]},
{mod, {bcrypt_app, []}},
{applications, [kernel, stdlib, crypto]},
@@ -10,7 +10,7 @@
{default_log_rounds, 12},
% Mechanism to use 'nif' or 'port'
- {mechanism, port},
+ {mechanism, nif},
% Size of port program pool
{pool_size, 4}
diff --git a/src/bcrypt_nif.erl b/src/bcrypt_nif.erl
index 425c2ce..af83b0e 100644
--- a/src/bcrypt_nif.erl
+++ b/src/bcrypt_nif.erl
@@ -22,7 +22,7 @@
%% API
-export([init/0]).
--export([gen_salt/1, hashpw/2]).
+-export([gen_salt/1, hashpw/5, create_ctx/0]).
-on_load(init/0).
@@ -61,12 +61,24 @@
nif_stub_error(?LINE).
%%--------------------------------------------------------------------
-%% @doc Hash the specified password and the salt using the OpenBSD
-%% Blowfish password hashing algorithm. Returns the hashed password.
-%% @spec hashpw(Password::binary(), Salt::binary()) -> string()
+%% @doc Create a context which hashes passwords in a separate thread.
+%% @spec create_ctx() -> term()
%% @end
%%--------------------------------------------------------------------
-hashpw(_Password, _Salt) ->
+create_ctx() ->
+ nif_stub_error(?LINE).
+
+%%--------------------------------------------------------------------
+%% @doc Hash the specified password and the salt using the OpenBSD
+%% Blowfish password hashing algorithm. Returns the hashed password.
+%% @spec hashpw(Ctx::term(),
+%% Ref::reference(),
+%% Pid::pid(),
+%% Password::binary(),
+%% Salt::binary()) -> string()
+%% @end
+%%--------------------------------------------------------------------
+hashpw(_Ctx, _Ref, _Pid, _Password, _Salt) ->
nif_stub_error(?LINE).
nif_stub_error(Line) ->
diff --git a/src/bcrypt_nif_worker.erl b/src/bcrypt_nif_worker.erl
index a3bffd8..e849597 100644
--- a/src/bcrypt_nif_worker.erl
+++ b/src/bcrypt_nif_worker.erl
@@ -13,7 +13,10 @@
-export([init/1, code_change/3, terminate/2,
handle_call/3, handle_cast/2, handle_info/2]).
--record(state, {default_log_rounds}).
+-record(state, {
+ default_log_rounds,
+ context
+ }).
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@@ -25,7 +28,8 @@
init([]) ->
{ok, Default} = application:get_env(bcrypt, default_log_rounds),
- {ok, #state{default_log_rounds = Default}}.
+ Ctx = bcrypt_nif:create_ctx(),
+ {ok, #state{default_log_rounds = Default, context = Ctx}}.
terminate(shutdown, _) -> ok.
@@ -33,8 +37,15 @@
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
handle_call({gen_salt, R}, _From, State) ->
{reply, {ok, bcrypt_nif:gen_salt(R)}, State};
-handle_call({hashpw, Password, Salt}, _From, State) ->
- {reply, {ok, bcrypt_nif:hashpw(Password, Salt)}, State};
+handle_call({hashpw, Password, Salt}, _From, #state{context=Ctx}=State) ->
+ Ref = make_ref(),
+ ok = bcrypt_nif:hashpw(Ctx, Ref, self(), Password, Salt),
+ receive
+ {ok, Ref, Result} ->
+ {reply, {ok, Result}, State};
+ {error, Ref, Result} ->
+ {reply, {error, Result}, State}
+ end;
handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
handle_info(Msg, _) -> exit({unknown_info, Msg}).