Hash passwords asynchronously in NIF
diff --git a/c_src/bcrypt_nif.c b/c_src/bcrypt_nif.c
index 076b4ba..4186ac7 100644
--- a/c_src/bcrypt_nif.c
+++ b/c_src/bcrypt_nif.c
@@ -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>
@@ -40,15 +41,32 @@
     return task;
 }
 
-task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref)
+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;
 }
@@ -97,6 +115,7 @@
         task = (task_t*)async_queue_pop(ctx->queue);
 
         if (task->type == SHUTDOWN) {
+            free_task(task);
             break;
         } else if (task->type == HASH) {
             result = hashpw(task);
@@ -108,8 +127,6 @@
         free_task(task);
     }
 
-    // Cleanup the shutdown task
-    free_task(task);
     return NULL;
 }
 
@@ -140,24 +157,33 @@
 
 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);
-    }
 
-    return enif_make_string(env, ret, ERL_NIF_LATIN1);
+    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[])
@@ -181,7 +207,7 @@
 static ErlNifFunc bcrypt_nif_funcs[] =
 {
     {"encode_salt", 2, bcrypt_encode_salt},
-    {"hashpw", 2, bcrypt_hashpw},
+    {"hashpw", 5, bcrypt_hashpw},
     {"create_ctx", 0, bcrypt_create_ctx},
 };
 
@@ -191,7 +217,7 @@
     task_t *task = alloc_task(SHUTDOWN);
     void   *result = NULL;
 
-    async_queue_push(ctx->queue, (void*)task);
+    async_queue_push(ctx->queue, task);
     enif_thread_join(ctx->tid, &result);
     async_queue_destroy(ctx->queue);
     enif_thread_opts_destroy(ctx->topts);
diff --git a/src/bcrypt.erl b/src/bcrypt.erl
index 985f55a..86fa4e9 100644
--- a/src/bcrypt.erl
+++ b/src/bcrypt.erl
@@ -6,7 +6,7 @@
 %% API
 -export([start/0, stop/0]).
 -export([mechanism/0]).
--export([gen_salt/0, gen_salt/1, hashpw/2, create_ctx/0]).
+-export([gen_salt/0, gen_salt/1, hashpw/2]).
 
 start() -> application:start(bcrypt).
 stop()  -> application:stop(bcrypt).
@@ -18,7 +18,6 @@
 gen_salt() -> do_gen_salt(mechanism()).
 gen_salt(Rounds) -> do_gen_salt(mechanism(), Rounds).
 hashpw(Password, Salt) -> do_hashpw(mechanism(), Password, Salt).
-create_ctx() -> do_create_ctx(mechanism()).
 
 do_gen_salt(nif)  -> bcrypt_nif_worker:gen_salt();
 do_gen_salt(port) -> bcrypt_pool:gen_salt().
@@ -28,6 +27,3 @@
 
 do_hashpw(nif, Password, Salt)  -> bcrypt_nif_worker:hashpw(Password, Salt);
 do_hashpw(port, Password, Salt) -> bcrypt_pool:hashpw(Password, Salt).
-
-do_create_ctx(nif) -> bcrypt_nif_worker:create_ctx();
-do_create_ctx(port) -> ok.
diff --git a/src/bcrypt_nif.erl b/src/bcrypt_nif.erl
index 1fb7181..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, create_ctx/0]).
+-export([gen_salt/1, hashpw/5, create_ctx/0]).
 
 -on_load(init/0).
 
@@ -71,10 +71,14 @@
 %%--------------------------------------------------------------------
 %% @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()
+%% @spec hashpw(Ctx::term(),
+%%              Ref::reference(),
+%%              Pid::pid(),
+%%              Password::binary(),
+%%              Salt::binary()) -> string()
 %% @end
 %%--------------------------------------------------------------------
-hashpw(_Password, _Salt) ->
+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 0765ad3..e849597 100644
--- a/src/bcrypt_nif_worker.erl
+++ b/src/bcrypt_nif_worker.erl
@@ -8,13 +8,15 @@
 -export([start_link/0]).
 -export([gen_salt/0, gen_salt/1]).
 -export([hashpw/2]).
--export([create_ctx/0]).
 
 %% gen_server
 -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, [], []).
 
@@ -23,12 +25,11 @@
     gen_server:call(?MODULE, {gen_salt, Rounds}, infinity).
 hashpw(Password, Salt) ->
     gen_server:call(?MODULE, {hashpw, Password, Salt}, infinity).
-create_ctx() ->
-    gen_server:call(?MODULE, create_ctx, infinity).
 
 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.
 
@@ -36,10 +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(create_ctx, _From, State) ->
-    {reply, {ok, bcrypt_nif:create_ctx()}, 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}).