diff --git a/src/sbbs3/js_request.c b/src/sbbs3/js_request.c
new file mode 100644
index 0000000000000000000000000000000000000000..2c270cb234ee1dc05f7615bdaf0e5ca363d0b999
--- /dev/null
+++ b/src/sbbs3/js_request.c
@@ -0,0 +1,274 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __unix__
+	#define XP_UNIX
+#else
+	#define XP_PC
+	#define XP_WIN
+#endif
+
+#include <jsapi.h>
+#include "threadwrap.h"
+#include "js_request.h"
+
+#ifdef DEBUG_JS_REQUESTS
+
+#define DEBUG
+#define JS_THREADSAFE
+#include <jscntxt.h>
+
+enum last_request_type {
+	 LAST_REQUEST_TYPE_NONE
+	,LAST_REQUEST_TYPE_BEGIN
+	,LAST_REQUEST_TYPE_END
+	,LAST_REQUEST_TYPE_SUSPEND
+	,LAST_REQUEST_TYPE_RESUME
+};
+static const char *type_names[5]={"None", "BeginRequest", "EndRequest", "SuspendRequest", "ResumeRequest"};
+
+struct request_log {
+	JSContext*				cx;
+	enum last_request_type	type;
+	const char*				file;
+	unsigned long			line;
+	struct request_log*		next;
+	struct request_log*		prev;
+};
+
+static struct request_log* first_request;
+static int initialized=0;
+static pthread_mutex_t	req_mutex;
+static char str[1024];
+
+static void logstr(void)
+{
+	FILE *f;
+
+	if((f=fopen("../data/logs/JS_RequestErrors.log", "a"))!=NULL) {
+		fputs(str, f);
+		fclose(f);
+	}
+	else {
+		fputs(str, stderr);
+	}
+}
+
+static struct request_log *match_request(JSContext *cx)
+{
+	struct request_log *ent;
+
+	for(ent=first_request; ent != NULL; ent=ent->next) {
+		if(ent->cx==cx)
+			return(ent);
+	}
+
+	ent=malloc(sizeof(struct request_log));
+	if(ent != NULL) {
+		memset(ent, 0, sizeof(struct request_log));
+		ent->cx=cx;
+		ent->type=LAST_REQUEST_TYPE_NONE;
+		ent->prev=NULL;
+		ent->next=first_request;
+		if(first_request != NULL) {
+			first_request->prev=ent;
+		}
+		first_request=ent;
+		return(ent);
+	}
+	return(NULL);
+}
+
+static void initialize_request(void)
+{
+	pthread_mutex_init(&req_mutex, NULL);
+	initialized=1;
+}
+
+void js_debug_beginrequest(JSContext *cx, const char *file, unsigned long line)
+{
+	struct request_log *req;
+
+	if(!initialized)
+		initialize_request();
+	pthread_mutex_lock(&req_mutex);
+	req=match_request(cx);
+	if(req==NULL) {
+		strcpy(str,"Missing req in Begin\n");
+		logstr();
+		return;
+	}
+	switch(req->type) {
+	case LAST_REQUEST_TYPE_NONE:
+	case LAST_REQUEST_TYPE_END:
+		break;
+	case LAST_REQUEST_TYPE_BEGIN:
+	case LAST_REQUEST_TYPE_SUSPEND:
+	case LAST_REQUEST_TYPE_RESUME:
+		sprintf(str,"Begin after %s from %s:%u at %s:%u\n",type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	}
+	switch(cx->requestDepth) {
+	case 0:
+		break;
+	case 1:
+	default:
+		sprintf(str,"depth=%u at Begin after %s from %s:%u at %s:%u\n",cx->requestDepth,type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	}
+
+	req->type=LAST_REQUEST_TYPE_BEGIN;
+	req->file=file;
+	req->line=line;
+	JS_BeginRequest(cx);
+	pthread_mutex_unlock(&req_mutex);
+}
+
+void js_debug_endrequest(JSContext *cx, const char *file, unsigned long line)
+{
+	struct request_log *req;
+
+	if(!initialized)
+		initialize_request();
+	pthread_mutex_lock(&req_mutex);
+	req=match_request(cx);
+	if(req==NULL) {
+		strcpy(str,"Missing req in End\n");
+		logstr();
+		return;
+	}
+	switch(req->type) {
+	case LAST_REQUEST_TYPE_BEGIN:
+		if(req->file) {
+			if(strcmp(req->file, file) != 0 || line < req->line) {
+				sprintf(str,"Suspicious End after %s from %s:%u at %s:%u\n",type_names[req->type],req->file,req->line,file,line);
+			}
+			break;
+		}
+	case LAST_REQUEST_TYPE_RESUME:
+		break;
+	case LAST_REQUEST_TYPE_NONE:
+	case LAST_REQUEST_TYPE_END:
+	case LAST_REQUEST_TYPE_SUSPEND:
+		sprintf(str,"End after %s from %s:%u at %s:%u\n",type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	}
+	switch(cx->requestDepth) {
+	case 0:
+	default:
+		sprintf(str,"depth=%u at End after %s from %s:%u at %s:%u\n",cx->requestDepth,type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	case 1:
+		break;
+	}
+
+	req->type=LAST_REQUEST_TYPE_END;
+	req->file=file;
+	req->line=line;
+	JS_EndRequest(cx);
+	if(req->prev != NULL)
+		req->prev->next=req->next;
+	if(req->next != NULL)
+		req->next->prev=req->prev;
+	if(first_request==req) {
+		if(req->prev != NULL)
+			first_request=req->prev;
+		else
+			first_request=req->next;
+	}
+	free(req);
+	pthread_mutex_unlock(&req_mutex);
+}
+
+jsrefcount js_debug_suspendrequest(JSContext *cx, const char *file, unsigned long line)
+{
+	struct request_log *req;
+	jsrefcount ret;
+
+	if(!initialized)
+		initialize_request();
+	pthread_mutex_lock(&req_mutex);
+	req=match_request(cx);
+	if(req==NULL) {
+		strcpy(str,"Missing req in Suspend\n");
+		logstr();
+		return;
+	}
+	switch(req->type) {
+	case LAST_REQUEST_TYPE_BEGIN:
+	case LAST_REQUEST_TYPE_RESUME:
+		break;
+	case LAST_REQUEST_TYPE_NONE:
+		if(req->file==NULL)			/* Assumed to be a provided request */
+			break;
+	case LAST_REQUEST_TYPE_END:
+	case LAST_REQUEST_TYPE_SUSPEND:
+		sprintf(str,"Suspend after %s from %s:%u at %s:%u\n",type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	}
+	switch(cx->requestDepth) {
+	case 0:
+	default:
+		sprintf(str,"depth=%u at Suspend after %s from %s:%u at %s:%u\n",cx->requestDepth,type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	case 1:
+		break;
+	}
+
+	req->type=LAST_REQUEST_TYPE_SUSPEND;
+	req->file=file;
+	req->line=line;
+	ret=JS_SuspendRequest(cx);
+	pthread_mutex_unlock(&req_mutex);
+	return(ret);
+}
+
+void js_debug_resumerequest(JSContext *cx, jsrefcount rc, const char *file, unsigned long line)
+{
+	struct request_log *req;
+
+	if(!initialized)
+		initialize_request();
+	pthread_mutex_lock(&req_mutex);
+	req=match_request(cx);
+	if(req==NULL) {
+		strcpy(str,"Missing req in Resume\n");
+		logstr();
+		return;
+	}
+	switch(req->type) {
+	case LAST_REQUEST_TYPE_SUSPEND:
+		break;
+	case LAST_REQUEST_TYPE_NONE:
+	case LAST_REQUEST_TYPE_END:
+	case LAST_REQUEST_TYPE_BEGIN:
+	case LAST_REQUEST_TYPE_RESUME:
+		sprintf(str,"Resume after %s from %s:%u at %s:%u\n",type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	}
+	switch(cx->requestDepth) {
+	case 1:
+	default:
+		sprintf(str,"depth=%u at Suspend after %s from %s:%u at %s:%u\n",cx->requestDepth,type_names[req->type],req->file,req->line,file,line);
+		logstr();
+		break;
+	case 0:
+		break;
+	}
+
+	req->type=LAST_REQUEST_TYPE_RESUME;
+	req->file=file;
+	req->line=line;
+	JS_ResumeRequest(cx, rc);
+	pthread_mutex_unlock(&req_mutex);
+}
+
+#endif
diff --git a/src/sbbs3/js_request.h b/src/sbbs3/js_request.h
new file mode 100644
index 0000000000000000000000000000000000000000..b7b74eafc1d33e623bf5f7b7a766e61f14201f01
--- /dev/null
+++ b/src/sbbs3/js_request.h
@@ -0,0 +1,29 @@
+#ifndef _JS_REQUEST_H_
+#define _JS_REQUEST_H_
+
+//#define DEBUG_JS_REQUESTS
+
+#ifdef DEBUG_JS_REQUESTS
+#ifdef __cplusplus
+extern "C" {
+#endif
+void js_debug_beginrequest(JSContext *cx, const char *file, unsigned long line);
+void js_debug_endrequest(JSContext *cx, const char *file, unsigned long line);
+jsrefcount js_debug_suspendrequest(JSContext *cx, const char *file, unsigned long line);
+void js_debug_resumerequest(JSContext *cx, jsrefcount rc, const char *file, unsigned long line);
+#ifdef __cplusplus
+}
+#endif
+
+#define JS_BEGINREQUEST(cx)			js_debug_beginrequest(cx, __FILE__, __LINE__)
+#define JS_ENDREQUEST(cx)			js_debug_endrequest(cx, __FILE__, __LINE__)
+#define JS_SUSPENDREQUEST(cx)		js_debug_suspendrequest(cx, __FILE__, __LINE__)
+#define JS_RESUMEREQUEST(cx, rf)	js_debug_resumerequest(cx, rf, __FILE__, __LINE__)
+#else
+#define JS_BEGINREQUEST(cx)	JS_BeginRequest(cx);
+#define JS_ENDREQUEST(cx)	JS_EndRequest(cx);
+#define JS_SUSPENDREQUEST(cx)	JS_SuspendRequest(cx);
+#define JS_RESUMEREQUEST(cx, rf)	JS_ResumeRequest(cx, rf);
+#endif
+
+#endif
diff --git a/src/sbbs3/objects.mk b/src/sbbs3/objects.mk
index ece91339802bfe7b386389bdbd36384a3100888d..7fba0c22efa1056ae08590cb44ac7513e39d5ace 100644
--- a/src/sbbs3/objects.mk
+++ b/src/sbbs3/objects.mk
@@ -50,6 +50,7 @@ OBJS	=	$(MTOBJODIR)$(DIRSEP)ansiterm$(OFILE) \
 			$(MTOBJODIR)$(DIRSEP)js_msg_area$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)js_msgbase$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)js_queue$(OFILE)\
+			$(MTOBJODIR)$(DIRSEP)js_request$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)js_rtpool$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)js_server$(OFILE)\
 			$(MTOBJODIR)$(DIRSEP)js_socket$(OFILE)\