--- old/./README.md	2013-02-02 20:44:55.521961951 +0000
+++ new/./README.md	2013-02-02 20:44:55.530131944 +0000
@@ -220,7 +220,7 @@
 function.  Return an encoded object with a `res` member, or use one of the
 error/exception patterns.
 
-### nvlist_t *v8plus_c_static_method_f(nvlist_t *ap)
+### nvlist_t *v8plus_c_static_method_f(const nvlist_t *ap)
 
 In addition to methods on the native objects returned by your constructor,
 you can also provide a set of functions on the native binding object itself.
@@ -624,7 +624,7 @@
 
 The other asynchronous mechanism is the Node.js `EventEmitter` model.  This
 model requires some assistance from JavaScript code, because v8+ native
-objects no not inherit from `EventEmitter`.  To make this work, you will
+objects do not inherit from `EventEmitter`.  To make this work, you will
 need to create a JavaScript object (the object your consumers actually use)
 that inherits from `EventEmitter`, hang your native object off this object,
 and populate the native object with an appropriate method that will cause
--- old/./v8plus_impl.h	2013-02-02 20:44:55.744405043 +0000
+++ new/./v8plus_impl.h	2013-02-02 20:44:55.749734450 +0000
@@ -9,6 +9,8 @@
 #include <stdarg.h>
 #include <libnvpair.h>
 #include <v8.h>
+#include <uv.h>
+#include <queue>
 #include <unordered_map>
 #include "v8plus_glue.h"
 
@@ -29,8 +31,23 @@
 #define	NODE_MAKECALLBACK_RETURN
 #endif
 
+typedef struct v8plus_async_call {
+	void *ac_cop;
+	const char *ac_name;
+	const nvlist_t *ac_lp;
+
+	pthread_cond_t ac_cv;
+	pthread_mutex_t ac_mtx;
+
+	boolean_t ac_run;
+	nvlist_t *ac_return;
+} v8plus_async_call_t;
+
+extern "C" void v8plus_async_callback(uv_async_t *, int);
+
 namespace v8plus {
 
+
 class ObjectWrap;
 
 class ObjectWrap : public node::ObjectWrap {
@@ -41,6 +58,9 @@
 	v8::Handle<v8::Value> call(const char *, int, v8::Handle<v8::Value>[]);
 	void public_Ref(void);
 	void public_Unref(void);
+	static v8plus_async_call_t *next_async_call(void);
+	static void post_async_call(v8plus_async_call_t *);
+	static boolean_t in_event_thread(void);
 
 private:
 	static v8::Persistent<v8::Function> _constructor;
@@ -49,6 +69,12 @@
 	static std::unordered_map<void *, ObjectWrap *> _objhash;
 	void *_c_impl;
 
+	static uv_async_t _uv_async;
+	static pthread_mutex_t _callq_mutex;
+	static std::queue<v8plus_async_call_t *> _callq;
+	static boolean_t _crossthread_init_done;
+	static unsigned long _uv_event_thread;
+
 	ObjectWrap() : _c_impl(NULL) {};
 	~ObjectWrap();
 
--- old/./v8plus_objectwrap.cc	2013-02-02 20:44:55.861364333 +0000
+++ new/./v8plus_objectwrap.cc	2013-02-02 20:44:55.866714700 +0000
@@ -18,6 +18,12 @@
 v8plus_static_descr_t *v8plus::ObjectWrap::_stbl;
 std::unordered_map<void *, v8plus::ObjectWrap *> v8plus::ObjectWrap::_objhash;
 
+uv_async_t v8plus::ObjectWrap::_uv_async;
+pthread_mutex_t v8plus::ObjectWrap::_callq_mutex;
+std::queue<v8plus_async_call_t *> v8plus::ObjectWrap::_callq;
+boolean_t v8plus::ObjectWrap::_crossthread_init_done = _B_FALSE;
+unsigned long v8plus::ObjectWrap::_uv_event_thread;
+
 static char *
 function_name(const char *lambda)
 {
@@ -113,6 +119,24 @@
 	if ((c_args = v8plus::v8_Arguments_to_nvlist(args)) == NULL)
 		return (V8PLUS_THROW_DEFAULT());
 
+	if (_crossthread_init_done == _B_FALSE) {
+		/*
+		 * Initialise structures for off-event-loop method calls.
+		 *
+		 * Note that uv_async_init() must be called inside the libuv
+		 * Event Loop, so we do it here.  We also want to record the
+		 * thread ID of the Event Loop thread so as to determine what
+		 * kind of method calls to make later.
+		 */
+		_uv_event_thread = pthread_self();
+		if (uv_async_init(uv_default_loop(), &_uv_async,
+		    v8plus_async_callback) != 0)
+			v8plus_panic("unable to initialise uv_async_t");
+		if (pthread_mutex_init(&_callq_mutex, NULL) != 0)
+			v8plus_panic("unable to initialise mutex");
+		_crossthread_init_done = _B_TRUE;
+	}
+
 	c_excp = v8plus_ctor(c_args, &op->_c_impl);
 	nvlist_free(c_args);
 	if (op->_c_impl == NULL) {
@@ -312,6 +336,48 @@
 	this->Unref();
 }
 
+boolean_t
+v8plus::ObjectWrap::in_event_thread(void)
+{
+	if (_crossthread_init_done != _B_TRUE)
+		v8plus_panic("cross thread call init not done!");
+
+	return (_uv_event_thread == pthread_self() ? _B_TRUE : _B_FALSE);
+}
+
+v8plus_async_call_t *
+v8plus::ObjectWrap::next_async_call(void)
+{
+	v8plus_async_call_t *ret = NULL;
+
+	if (pthread_mutex_lock(&_callq_mutex) != 0)
+		v8plus_panic("could not lock callq mutex");
+
+	if (!_callq.empty()) {
+		ret = _callq.front();
+		_callq.pop();
+	}
+
+	if (pthread_mutex_unlock(&_callq_mutex) != 0)
+		v8plus_panic("could not release callq mutex");
+
+	return (ret);
+}
+
+void
+v8plus::ObjectWrap::post_async_call(v8plus_async_call_t *ac)
+{
+	if (pthread_mutex_lock(&_callq_mutex) != 0)
+		v8plus_panic("could not lock callq mutex");
+
+	_callq.push(ac);
+
+	if (pthread_mutex_unlock(&_callq_mutex) != 0)
+		v8plus_panic("could not release callq mutex");
+
+	uv_async_send(&_uv_async);
+}
+
 extern "C" void
 init(v8::Handle<v8::Object> target)
 {
--- old/./v8plus_subr.cc	2013-02-02 20:44:55.984717964 +0000
+++ new/./v8plus_subr.cc	2013-02-02 20:44:55.990081637 +0000
@@ -29,6 +29,7 @@
 static uint64_t cbnext;
 static void (*__real_nvlist_free)(nvlist_t *);
 
+
 static const char *
 cstr(const v8::String::Utf8Value &v)
 {
@@ -453,7 +454,7 @@
 }
 
 extern "C" nvlist_t *
-v8plus_method_call(void *cop, const char *name, const nvlist_t *lp)
+v8plus_method_call_direct(void *cop, const char *name, const nvlist_t *lp)
 {
 	v8plus::ObjectWrap *op = v8plus::ObjectWrap::objlookup(cop);
 	const int max_argc = nvlist_length(lp);
@@ -462,6 +463,9 @@
 	v8::Handle<v8::Value> res;
 	nvlist_t *rp;
 
+	if (v8plus::ObjectWrap::in_event_thread() != _B_TRUE)
+		v8plus_panic("direct method call outside of event loop");
+
 	argc = max_argc;
 	nvlist_to_v8_argv(lp, &argc, argv);
 
@@ -485,6 +489,77 @@
 	return (rp);
 }
 
+extern "C" void
+v8plus_async_callback(uv_async_t *async, __attribute__((unused)) int status)
+{
+	if (v8plus::ObjectWrap::in_event_thread() != _B_TRUE)
+		v8plus_panic("async callback called outside of event loop");
+
+	for (;;) {
+		v8plus_async_call_t *ac;
+
+		if ((ac = v8plus::ObjectWrap::next_async_call()) == NULL)
+			break;
+
+		if (pthread_mutex_lock(&ac->ac_mtx) != 0)
+			v8plus_panic("could not lock async call mutex");
+
+		if (ac->ac_run == _B_TRUE)
+			v8plus_panic("async call already run");
+
+		ac->ac_return = v8plus_method_call_direct(ac->ac_cop,
+		    ac->ac_name, ac->ac_lp);
+		ac->ac_run = _B_TRUE;
+
+		if (pthread_cond_broadcast(&ac->ac_cv) != 0)
+			v8plus_panic("could not signal async call condvar");
+		if (pthread_mutex_unlock(&ac->ac_mtx) != 0)
+			v8plus_panic("could not unlock async call mutex");
+	}
+}
+
+extern "C" nvlist_t *
+v8plus_method_call(void *cop, const char *name, const nvlist_t *lp)
+{
+	v8plus_async_call_t ac;
+
+	if (v8plus::ObjectWrap::in_event_thread() == _B_TRUE) {
+		/*
+		 * We're running in the event loop thread, so we can make the
+		 * call directly.
+		 */
+		return (v8plus_method_call_direct(cop, name, lp));
+	}
+
+	/*
+	 * As we cannot manipulate v8plus/V8/Node structures directly from
+	 * outside the event loop thread, we push the call arguments onto a
+	 * queue and post to the event loop thread.  We then sleep on our
+	 * condition variable until the event loop thread makes the call
+	 * for us and wakes us up.
+	 */
+	ac.ac_cop = cop;
+	ac.ac_name = name;
+	ac.ac_lp = lp;
+	if (pthread_mutex_init(&ac.ac_mtx, NULL) != 0)
+		v8plus_panic("could not init async call mutex");
+	if (pthread_cond_init(&ac.ac_cv, NULL) != 0)
+		v8plus_panic("could not init async call condvar");
+	ac.ac_run = _B_FALSE;
+	ac.ac_return = NULL;
+
+	v8plus::ObjectWrap::post_async_call(&ac);
+
+	if (pthread_mutex_lock(&ac.ac_mtx) != 0)
+		v8plus_panic("could not lock async call mutex");
+	while (ac.ac_run == _B_FALSE) {
+		if (pthread_cond_wait(&ac.ac_cv, &ac.ac_mtx) != 0)
+			v8plus_panic("could not wait on async call condvar");
+	}
+
+	return (ac.ac_return);
+}
+
 extern "C" int
 nvlist_lookup_v8plus_jsfunc(const nvlist_t *lp, const char *name,
     v8plus_jsfunc_t *vp)
