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}).