Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How about thread safety? #228

Open
IdWV opened this issue Sep 28, 2024 · 7 comments
Open

How about thread safety? #228

IdWV opened this issue Sep 28, 2024 · 7 comments

Comments

@IdWV
Copy link
Contributor

IdWV commented Sep 28, 2024

When looking through the code I have the impression that the code should be thread safe, as long as you only access the uc_vm_t object from the thread where the uc_vm_execute() function is running.

With one exception, all uc_resource_type_t *objects in the modules in the /lib directory are global. I think this means that running 2 scripts using the same module in different threads can give undesired results.

To solve this, I think a module should be able to store a pointer in uc_vm_t (together with a cleanup function), and request that back. Maybe by using that cleanup function pointer as key.

I couldn't find it, is such a storage implemented?

@jow-
Copy link
Owner

jow- commented Sep 28, 2024

Yes, this is indeed a limitation in the current code. The fix is rather straightforward though. The resource types are already stored per vm context, but due to lazyness the result pointer got stored in a global variable for easy access.

Affected modules can be refactored like this:

diff --git a/lib/fs.c b/lib/fs.c
index e3f8a4a..23c5495 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -71,7 +71,6 @@
 #define err_return(err) do { last_error = err; return NULL; } while(0)
 
 //static const uc_ops *ops;
-static uc_resource_type_t *file_type, *proc_type, *dir_type;
 
 static int last_error = 0;
 
@@ -511,7 +510,7 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
 	if (!fp)
 		err_return(errno);
 
-	return uc_resource_new(proc_type, fp);
+	return uc_resource_new(ucv_resource_type_lookup(vm, "fs.proc"), fp);
 }
 
 
@@ -1178,7 +1177,7 @@ uc_fs_open(uc_vm_t *vm, size_t nargs)
 		err_return(i);
 	}
 
-	return uc_resource_new(file_type, fp);
+	return uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), fp);
 }
 
 /**
@@ -1237,7 +1236,7 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
 	if (!fp)
 		err_return(errno);
 
-	return uc_resource_new(file_type, fp);
+	return uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), fp);
 }
 
 
@@ -1438,7 +1437,7 @@ uc_fs_opendir(uc_vm_t *vm, size_t nargs)
 	if (!dp)
 		err_return(errno);
 
-	return uc_resource_new(dir_type, dp);
+	return uc_resource_new(ucv_resource_type_lookup(vm, "fs.dir"), dp);
 }
 
 /**
@@ -2400,7 +2399,7 @@ uc_fs_mkstemp(uc_vm_t *vm, size_t nargs)
 		err_return(errno);
 	}
 
-	return uc_resource_new(file_type, fp);
+	return uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), fp);
 }
 
 /**
@@ -2770,8 +2769,8 @@ uc_fs_pipe(uc_vm_t *vm, size_t nargs)
 
 	rv = ucv_array_new_length(vm, 2);
 
-	ucv_array_push(rv, uc_resource_new(file_type, rfp));
-	ucv_array_push(rv, uc_resource_new(file_type, wfp));
+	ucv_array_push(rv, uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), rfp));
+	ucv_array_push(rv, uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), wfp));
 
 	return rv;
 }
@@ -2873,11 +2872,11 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
 {
 	uc_function_list_register(scope, global_fns);
 
-	proc_type = uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
-	file_type = uc_type_declare(vm, "fs.file", file_fns, close_file);
-	dir_type = uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
+	uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
+	uc_type_declare(vm, "fs.file", file_fns, close_file);
+	uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
 
-	ucv_object_add(scope, "stdin", uc_resource_new(file_type, stdin));
-	ucv_object_add(scope, "stdout", uc_resource_new(file_type, stdout));
-	ucv_object_add(scope, "stderr", uc_resource_new(file_type, stderr));
+	ucv_object_add(scope, "stdin", uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), stdin));
+	ucv_object_add(scope, "stdout", uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), stdout));
+	ucv_object_add(scope, "stderr", uc_resource_new(ucv_resource_type_lookup(vm, "fs.file"), stderr));
 }

Along such a refactoring, it probably makes sense to introduce a new instantiation function for resource values as well, something along the lines of

static uc_value_t *
ucv_resource_create(uc_vm_t *vm, const char *typename, void *value)
{
    uc_resource_type_t *t = NULL;

    if (typename && (t = ucv_resource_type_lookup(vm, typename)) == NULL)
        return NULL;

    return uc_resource_new(t, value);
}

Which would turn the refactoring into

diff --git a/lib/fs.c b/lib/fs.c
index e3f8a4a..e7dc52f 100644
--- a/lib/fs.c
+++ b/lib/fs.c
@@ -71,7 +71,6 @@
 #define err_return(err) do { last_error = err; return NULL; } while(0)
 
 //static const uc_ops *ops;
-static uc_resource_type_t *file_type, *proc_type, *dir_type;
 
 static int last_error = 0;
 
@@ -511,7 +510,7 @@ uc_fs_popen(uc_vm_t *vm, size_t nargs)
 	if (!fp)
 		err_return(errno);
 
-	return uc_resource_new(proc_type, fp);
+	return ucv_resource_create(vm, "fs.proc", fp);
 }
 
 
@@ -1178,7 +1177,7 @@ uc_fs_open(uc_vm_t *vm, size_t nargs)
 		err_return(i);
 	}
 
-	return uc_resource_new(file_type, fp);
+	return ucv_resource_create(vm, "fs.file", fp);
 }
 
 /**
@@ -1237,7 +1236,7 @@ uc_fs_fdopen(uc_vm_t *vm, size_t nargs)
 	if (!fp)
 		err_return(errno);
 
-	return uc_resource_new(file_type, fp);
+	return ucv_resource_create(vm, "fs.file", fp);
 }
 
 
@@ -1438,7 +1437,7 @@ uc_fs_opendir(uc_vm_t *vm, size_t nargs)
 	if (!dp)
 		err_return(errno);
 
-	return uc_resource_new(dir_type, dp);
+	return ucv_resource_create(vm, "fs.dir", dp);
 }
 
 /**
@@ -2400,7 +2399,7 @@ uc_fs_mkstemp(uc_vm_t *vm, size_t nargs)
 		err_return(errno);
 	}
 
-	return uc_resource_new(file_type, fp);
+	return ucv_resource_create(vm, "fs.file", fp);
 }
 
 /**
@@ -2770,8 +2769,8 @@ uc_fs_pipe(uc_vm_t *vm, size_t nargs)
 
 	rv = ucv_array_new_length(vm, 2);
 
-	ucv_array_push(rv, uc_resource_new(file_type, rfp));
-	ucv_array_push(rv, uc_resource_new(file_type, wfp));
+	ucv_array_push(rv, ucv_resource_create(vm, "fs.file", rfp));
+	ucv_array_push(rv, ucv_resource_create(vm, "fs.file", wfp));
 
 	return rv;
 }
@@ -2873,11 +2872,11 @@ void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
 {
 	uc_function_list_register(scope, global_fns);
 
-	proc_type = uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
-	file_type = uc_type_declare(vm, "fs.file", file_fns, close_file);
-	dir_type = uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
+	uc_type_declare(vm, "fs.proc", proc_fns, close_proc);
+	uc_type_declare(vm, "fs.file", file_fns, close_file);
+	uc_type_declare(vm, "fs.dir", dir_fns, close_dir);
 
-	ucv_object_add(scope, "stdin", uc_resource_new(file_type, stdin));
-	ucv_object_add(scope, "stdout", uc_resource_new(file_type, stdout));
-	ucv_object_add(scope, "stderr", uc_resource_new(file_type, stderr));
+	ucv_object_add(scope, "stdin", ucv_resource_create(vm, "fs.file", stdin));
+	ucv_object_add(scope, "stdout", ucv_resource_create(vm, "fs.file", stdout));
+	ucv_object_add(scope, "stderr", ucv_resource_create(vm, "fs.file", stderr));
 }

Would you be willing to spin a PR applying this refactoring to the various lib/*.c modules?

@IdWV
Copy link
Contributor Author

IdWV commented Sep 29, 2024

Well, I can try. I'm a dinosaur which learned programming in K&R C. But never used git(hub), so 'spinning' a pull request is new to me.

@jow-
Copy link
Owner

jow- commented Sep 29, 2024

I see :) I'll see if I can do the changes tonight then. In the meanwhile I would be good if you could manually apply the changes outlined above and confirm whether they indeed solve your thread safety issues.

@IdWV
Copy link
Contributor Author

IdWV commented Sep 30, 2024

I can confirm that this patch solves the module reuse problem for the fs module. Multithreading also works, except for the uc_fs_error()/err_return() function. For the fs.file and fs.dir resource that could be solved by not storing the FILE or DIR pointer in the uc_resource_t, but a { FILE *fp; int last_error; } struct pointer.
Looking through the lib dir I see almost every module has a static last_error value/struct. Maybe it's an idea to use the _Thread_local keyword? If it works it is an easy fix. But I don't know if it pulls libpthread.so into a singlethreaded program. And of course it's ugly to bind a variable to a thread instead of the vm object.

@IdWV
Copy link
Contributor Author

IdWV commented Sep 30, 2024

Further searching learned that the vm_registry seems to be designed for this. Is there somewhere documentation available about all these API functions?

@sebastianertz
Copy link
Contributor

There are so many global variables, it will be a lot of work to remove them.

This is what I found quickly:

// lib/fs.c
static uc_resource_type_t *file_type, *proc_type, *dir_type;
static int last_error = 0;
// lib/log.c
static char log_ident[32];
// lib/math.c
static bool srand_called = false;
// lib/nl80211.c
static struct {
	int code;
	char *msg;
} last_error;

static uc_resource_type_t *listener_type;
static uc_value_t *listener_registry;
static uc_vm_t *listener_vm;
// lib/resolv.c
static struct {
	int code;
	char *msg;
} last_error;
// lib/rtnl.c
static struct {
	int code;
	char *msg;
} last_error;
static uc_resource_type_t *listener_type;
static uc_value_t *listener_registry;
static uc_vm_t *listener_vm;
// lib/socket.c
static struct {
	int code;
	char *msg;
} last_error;
// lib/ubus.c
static struct {
	enum ubus_msg_status code;
	char *msg;
} last_error;
static uc_resource_type_t *subscriber_type;
static uc_resource_type_t *listener_type;
static uc_resource_type_t *request_type;
static uc_resource_type_t *notify_type;
static uc_resource_type_t *object_type;
static uc_resource_type_t *defer_type;
static uc_resource_type_t *conn_type;
static uint64_t n_cb_active;
static bool have_own_uloop;
static struct blob_buf buf;
// lib/uci.c
static int last_error = 0;
static uc_resource_type_t *cursor_type;
// lib/uloop.c
static uc_resource_type_t *timer_type, *handle_type, *process_type, *task_type, *pipe_type;
static uc_resource_type_t *interval_type;
static uc_resource_type_t *signal_type;
static uc_value_t *object_registry;
static int last_error = 0;
// types.c
uc_list_t uc_object_iterators = {
	.prev = &uc_object_iterators,
	.next = &uc_object_iterators
};
// vm.c
static uc_value_t *exception_prototype = NULL;
static uc_resource_type_t uc_vm_object_iterator_type = {
	.name = "object iterator",
	.free = uc_vm_object_iterator_free
};

@IdWV
Copy link
Contributor Author

IdWV commented Oct 8, 2024

True. But it's not hard to do, and for my purposes the OpenWrt only modules are not important. That removes about 80% of the code to be adjusted, when I'm not mistaken.

But that 'uc_list_t uc_object_iterators' thing is a nasty one. It is used in a static object delete function (static ucv_free_object_entry(), in types.c), called from an external library json-c, without possibility to provide a uc_vm_t pointer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants