mirror of
https://github.com/apple/pkl.git
synced 2026-08-10 06:08:43 +02:00
Fix various issues in libpkl (#1805)
* Fix possible SIGSEGV from host process. - Require that the same `pkl_exec_t` be used in the same OS thread; returning meaningful error if this fails * Remove `System.exitProcess(1)` logic in NativeTransport; this would kill the host process too and isn't an appropriate action for a received ProtocolException * Allow multiple calls to pkl_init without pkl_close; this limitation doesn't really make any sense * Prefer calling methods defined in graal_isolate.h, instead of creating the same method via CEntryPoint * Add a CMakeLists.txt so that CLion can follow the code and provide proper diagnostics * Improve doc comments; describe params as either in or out
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
# This file is only used to configure CLion with proper include paths.
|
||||||
|
# Pkl builds everything through Gradle, not CMake.
|
||||||
|
cmake_minimum_required(VERSION 4.2)
|
||||||
|
project(pkl C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
|
file(GLOB NATIVE_LIB_INCLUDE_DIRS CONFIGURE_DEPENDS
|
||||||
|
libpkl/build/native-libs/*/include)
|
||||||
|
file(GLOB NATIVE_IMAGE_BUILD_DIRS CONFIGURE_DEPENDS
|
||||||
|
libpkl/build/tmp/native-image-build/*)
|
||||||
|
|
||||||
|
include_directories(${NATIVE_LIB_INCLUDE_DIRS})
|
||||||
|
include_directories(${NATIVE_IMAGE_BUILD_DIRS})
|
||||||
|
include_directories(libpkl/src/main/c/include)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE GENERATED_HEADER_FILES CONFIGURE_DEPENDS
|
||||||
|
libpkl/build/tmp/native-libs/*/*.h)
|
||||||
|
|
||||||
|
add_executable(pkl
|
||||||
|
${GENERATED_HEADER_FILES}
|
||||||
|
libpkl/src/main/c/include/pkl.h
|
||||||
|
libpkl/src/main/c/pkl.c
|
||||||
|
libpkl/src/nativeTest/c/test_pkl.c)
|
||||||
@@ -93,6 +93,14 @@ There is an IntelliJ plugin meant for development on the Pkl project itself loca
|
|||||||
|
|
||||||
See https://github.com/apple/pkl-project-commons?tab=readme-ov-file#internal-intellij-plugin[its readme] for instructions on how to set it up.
|
See https://github.com/apple/pkl-project-commons?tab=readme-ov-file#internal-intellij-plugin[its readme] for instructions on how to set it up.
|
||||||
|
|
||||||
|
== Working on libpkl
|
||||||
|
|
||||||
|
libpkl is a native library written in C, and is best written in CLion, or VSCode.
|
||||||
|
|
||||||
|
IntelliJ has limited support for editing C sources.
|
||||||
|
|
||||||
|
There is a CMakeLists.txt file whose only job is to configure CLion with proper include paths.
|
||||||
|
|
||||||
== Resources
|
== Resources
|
||||||
|
|
||||||
For automated build setup examples see our https://github.com/apple/pkl/blob/main/.github/[GitHub Actions] jobs like our https://github.com/apple/pkl/blob/main/.github/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically.
|
For automated build setup examples see our https://github.com/apple/pkl/blob/main/.github/[GitHub Actions] jobs like our https://github.com/apple/pkl/blob/main/.github/jobs/BuildNativeJob.pkl[BuildNativeJob.pkl], where we build Pkl automatically.
|
||||||
|
|||||||
@@ -511,7 +511,7 @@ spotless {
|
|||||||
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt"),
|
rootProject.file("build-logic/src/main/resources/license-header.star-block.txt"),
|
||||||
"// ",
|
"// ",
|
||||||
)
|
)
|
||||||
target("src/*/c/*.c", "src/*/c/*.h")
|
target("src/*/c/**/*.c", "src/*/c/**/*.h")
|
||||||
eclipseCdt(libs.versions.eclipseCdtFormat.get())
|
eclipseCdt(libs.versions.eclipseCdtFormat.get())
|
||||||
}
|
}
|
||||||
shell {
|
shell {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ extern "C" {
|
|||||||
#define PKL_EXPORT __attribute__((visibility("default")))
|
#define PKL_EXPORT __attribute__((visibility("default")))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define PKL_ERR_LOCK 1 /* Failed to create a mutex, or acquire a lock on a mutex */
|
#define PKL_ERR_THREAD 1 /* Called using the same pexec_t but from a different thread */
|
||||||
#define PKL_ERR_PROTOCOL 2 /* Failed to decode a message */
|
#define PKL_ERR_PROTOCOL 2 /* Failed to decode a message */
|
||||||
|
|
||||||
/** Error details that occurred during a method call */
|
/** Error details that occurred during a method call */
|
||||||
@@ -38,34 +38,36 @@ typedef struct {
|
|||||||
/**
|
/**
|
||||||
* Pkl executor instance that manages communication with the Pkl runtime.
|
* Pkl executor instance that manages communication with the Pkl runtime.
|
||||||
*
|
*
|
||||||
* Instances should be created via `pkl_init()` and destroyed via `pkl_close().`
|
* Instances should be created via `pkl_init` and destroyed via `pkl_close`.
|
||||||
*
|
*
|
||||||
* All operations on this struct are considered thread-safe and are synchronized via a mutex.
|
* All calls using this executor should be synchronized in the same thread.
|
||||||
*/
|
*/
|
||||||
typedef struct __pkl_exec_t pkl_exec_t;
|
typedef struct __pkl_exec_t pkl_exec_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback that gets called when a message is received from Pkl.
|
* The callback that gets called when a message is received from Pkl.
|
||||||
*
|
*
|
||||||
* Messages must be deserialized to Pkl's Message Passing API:
|
* Messages must be deserialized to Pkl's Message Passing API:
|
||||||
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
||||||
*
|
*
|
||||||
* @param length The length of the message bytes
|
* @param[in] length The length of the message bytes
|
||||||
* @param message The message itself
|
* @param[in] message The message itself
|
||||||
* @param userData User-defined data passed in from pkl_init.
|
* @param[in] userData User-defined data passed in from pkl_init.
|
||||||
*/
|
*/
|
||||||
typedef void (*pkl_message_response_handler)(unsigned int length, char *message,
|
typedef void (*pkl_message_response_handler)(unsigned int length, char *message,
|
||||||
void *userData);
|
void *userData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises and allocates a Pkl executor, writing it to the slot pointed by `exec`.
|
* Initializes and allocates a Pkl executor, writing it to the slot pointed by `exec`.
|
||||||
* Only one executor can exist at one time.
|
|
||||||
* Calling `pkl_init` multiple times without calling `pkl_close` in between results in an error.
|
|
||||||
*
|
*
|
||||||
* @param handler The callback that gets called when a message is received from Pkl.
|
* To clean up resources allocated by the executor, use `pkl_close()`.
|
||||||
* @param userData User-defined data that gets passed to handler.
|
*
|
||||||
* @param exec The pointer to write the created pkl_exec_t to.
|
* All calls using this executor should come from the same thread.
|
||||||
* @param error The pointer to write error details to.
|
*
|
||||||
|
* @param[in] handler The callback that gets called when a message is received from Pkl.
|
||||||
|
* @param[in] userData User-defined data that gets passed to handler.
|
||||||
|
* @param[out] exec The pointer to write the created pkl_exec_t to.
|
||||||
|
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||||
*
|
*
|
||||||
* @return 0 on success, non-zero on failure.
|
* @return 0 on success, non-zero on failure.
|
||||||
*/
|
*/
|
||||||
@@ -75,25 +77,34 @@ PKL_EXPORT int pkl_init(pkl_message_response_handler handler, void *userData,
|
|||||||
/**
|
/**
|
||||||
* Send a message to Pkl, providing the length and a pointer to the first byte.
|
* Send a message to Pkl, providing the length and a pointer to the first byte.
|
||||||
*
|
*
|
||||||
* Messages must be serialized to Pkl's Message Passing API:
|
* Messages must be serialized according to Pkl's Message Passing API:
|
||||||
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
* https://pkl-lang.org/main/current/bindings-specification/message-passing-api.html
|
||||||
*
|
*
|
||||||
* @param pexec The Pkl executor instance.
|
* If a message is incorrectly serialized, returns `PKL_ERR_PROTOCOL`.
|
||||||
* @param length The length of the message, in bytes.
|
* If called from a different thread than `pkl_exec_t`'s originating thread, returns
|
||||||
* @param message The message to send to Pkl.
|
* `PKL_ERR_THREAD`.
|
||||||
|
*
|
||||||
|
* @param[in] pexec The Pkl executor instance.
|
||||||
|
* @param[in] length The length of the message, in bytes.
|
||||||
|
* @param[in] message The message to send to Pkl.
|
||||||
|
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||||
*
|
*
|
||||||
* @return 0 on success, and non-zero otherwise.
|
* @return 0 on success, and non-zero otherwise.
|
||||||
*/
|
*/
|
||||||
PKL_EXPORT int pkl_send_message(pkl_exec_t *pexec, unsigned int length, char *message,
|
PKL_EXPORT int pkl_send_message(const pkl_exec_t *pexec, unsigned int length,
|
||||||
pkl_error_t *error);
|
char *message, pkl_error_t *error);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleans up any resources that were created as part of the `pkl_init` process
|
* Cleans up any resources that were created as part of the `pkl_init` process
|
||||||
* for our `pkl_exec_t` instance.
|
* for our `pkl_exec_t` instance.
|
||||||
*
|
*
|
||||||
* @param pexec The Pkl executor instance.
|
* If called from a different thread than `pkl_exec_t`'s originating thread, returns
|
||||||
|
* `PKL_ERR_THREAD`.
|
||||||
*
|
*
|
||||||
* @return 0 on success, -1 if `pexec` is NULL, and an error code otherwise.
|
* @param[in] pexec The Pkl executor instance.
|
||||||
|
* @param[out] error The pointer to write error details to. Can optionally be `NULL`.
|
||||||
|
*
|
||||||
|
* @return 0 on success, -1 if `pexec` is `NULL`, and an error code otherwise.
|
||||||
*/
|
*/
|
||||||
PKL_EXPORT int pkl_close(pkl_exec_t *pexec, pkl_error_t *error);
|
PKL_EXPORT int pkl_close(pkl_exec_t *pexec, pkl_error_t *error);
|
||||||
|
|
||||||
|
|||||||
+38
-139
@@ -32,19 +32,14 @@
|
|||||||
#define PKL_VERSION "0.0.0"
|
#define PKL_VERSION "0.0.0"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ReSharper disable once CppClassNeverUsed
|
||||||
struct __pkl_exec_t {
|
struct __pkl_exec_t {
|
||||||
#ifdef _WIN32
|
|
||||||
CRITICAL_SECTION mutex;
|
|
||||||
#else
|
|
||||||
pthread_mutex_t mutex;
|
|
||||||
#endif
|
|
||||||
graal_isolatethread_t *graal_isolatethread;
|
|
||||||
graal_isolate_t *isolate;
|
graal_isolate_t *isolate;
|
||||||
|
graal_isolatethread_t *isolateThread;
|
||||||
|
|
||||||
/**
|
/** The caller-supplied handler/userData from pkl_init, invoked only from `queue_thread`. */
|
||||||
* The caller-supplied handler/userData from pkl_init, invoked only from `queue_thread`.
|
|
||||||
*/
|
|
||||||
pkl_message_response_handler handler;
|
pkl_message_response_handler handler;
|
||||||
|
|
||||||
void *userData;
|
void *userData;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -54,12 +49,10 @@ struct __pkl_exec_t {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
static int pkl_is_initialized = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Polls for messages coming from Pkl and sends them back to the handler.
|
* Polls for messages coming from Pkl and sends them back to the handler.
|
||||||
*
|
*
|
||||||
* Ensures thats calls into Pkl are wrapped with graal_attach_thread/graal_detach_thread, and
|
* Ensures that calls into Pkl are wrapped with graal_attach_thread/graal_detach_thread, and
|
||||||
* calls back to the handler are _not_ inside the GraalVM thread context.
|
* calls back to the handler are _not_ inside the GraalVM thread context.
|
||||||
*/
|
*/
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -67,7 +60,7 @@ static DWORD WINAPI pkl_dispatch_worker(LPVOID arg) {
|
|||||||
#else
|
#else
|
||||||
static void* pkl_dispatch_worker(void *arg) {
|
static void* pkl_dispatch_worker(void *arg) {
|
||||||
#endif
|
#endif
|
||||||
pkl_exec_t *pexec = (pkl_exec_t*) arg;
|
const pkl_exec_t *pexec = (pkl_exec_t*) arg;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
graal_isolatethread_t *thread;
|
graal_isolatethread_t *thread;
|
||||||
@@ -78,7 +71,7 @@ static void* pkl_dispatch_worker(void *arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *message = NULL;
|
char *message = NULL;
|
||||||
int length = pkl_internal_poll_response(thread, &message);
|
const int length = pkl_internal_poll_response(thread, &message);
|
||||||
|
|
||||||
if (graal_detach_thread(thread) != 0) {
|
if (graal_detach_thread(thread) != 0) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
@@ -128,40 +121,6 @@ static void pkl_stop_dispatch_worker(pkl_exec_t *pexec) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pkl_runtime_cleanup(pkl_exec_t *pexec) {
|
|
||||||
// pkl_internal_server_stop unblocks queue_thread's pending/next poll call, so the join
|
|
||||||
// below is guaranteed to return; only then is it safe to tear down the isolate.
|
|
||||||
pkl_internal_server_stop(pexec->graal_isolatethread);
|
|
||||||
pkl_stop_dispatch_worker(pexec);
|
|
||||||
pkl_internal_close(pexec->graal_isolatethread);
|
|
||||||
pexec->graal_isolatethread = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pkl_unlock_mutex(pkl_exec_t *pexec) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
LeaveCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_unlock(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to unlock mutex.\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pkl_lock_mutex(pkl_exec_t *pexec, pkl_error_t *error) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
EnterCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_lock(&pexec->mutex) != 0) {
|
|
||||||
if (error != NULL) {
|
|
||||||
error->message = "failed to lock mutex";
|
|
||||||
}
|
|
||||||
return PKL_ERR_LOCK;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pkl_init(pkl_message_response_handler handler, void *userData,
|
int pkl_init(pkl_message_response_handler handler, void *userData,
|
||||||
pkl_exec_t **exec, pkl_error_t *error) {
|
pkl_exec_t **exec, pkl_error_t *error) {
|
||||||
if (handler == NULL) {
|
if (handler == NULL) {
|
||||||
@@ -176,92 +135,38 @@ int pkl_init(pkl_message_response_handler handler, void *userData,
|
|||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (pkl_is_initialized) {
|
|
||||||
if (error != NULL) {
|
|
||||||
error->message =
|
|
||||||
"pkl_init called multiple times without calling pkl_close";
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
pkl_exec_t *pexec = calloc(1, sizeof(pkl_exec_t));
|
pkl_exec_t *pexec = calloc(1, sizeof(pkl_exec_t));
|
||||||
if (pexec == NULL) {
|
if (pexec == NULL) {
|
||||||
fprintf(stderr, "failed to allocate pkl_exec_t\n");
|
fprintf(stderr, "failed to allocate pkl_exec_t\n");
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
InitializeCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_init(&pexec->mutex, NULL) != 0) {
|
|
||||||
if (error != NULL) {
|
|
||||||
error->message = "Failed to initialize mutex";
|
|
||||||
}
|
|
||||||
free(pexec);
|
|
||||||
*exec = NULL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pexec->handler = handler;
|
pexec->handler = handler;
|
||||||
pexec->userData = userData;
|
pexec->userData = userData;
|
||||||
|
|
||||||
pexec->graal_isolatethread = pkl_internal_init();
|
graal_isolate_t *isolate;
|
||||||
|
graal_isolatethread_t *isolateThread;
|
||||||
|
|
||||||
if (pexec->graal_isolatethread == NULL) {
|
if (graal_create_isolate(NULL, &isolate, &isolateThread) != 0) {
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = "Failed to allocate graal_isolatethread";
|
error->message = "Failed to create graal isolate thread";
|
||||||
}
|
}
|
||||||
#ifdef _WIN32
|
|
||||||
DeleteCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_destroy(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to destroy mutex.\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
free(pexec);
|
|
||||||
*exec = NULL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pexec->isolate = graal_get_isolate(pexec->graal_isolatethread);
|
|
||||||
if (pexec->isolate == NULL) {
|
|
||||||
if (error != NULL) {
|
|
||||||
error->message =
|
|
||||||
"Failed to resolve isolate from graal_isolatethread";
|
|
||||||
}
|
|
||||||
pkl_internal_close(pexec->graal_isolatethread);
|
|
||||||
#ifdef _WIN32
|
|
||||||
DeleteCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_destroy(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to destroy mutex.\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
free(pexec);
|
free(pexec);
|
||||||
*exec = NULL;
|
*exec = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
pexec->isolate = isolate;
|
||||||
|
pexec->isolateThread = isolateThread;
|
||||||
|
|
||||||
if (pkl_start_dispatch_worker(pexec, error) != 0) {
|
if (pkl_start_dispatch_worker(pexec, error) != 0) {
|
||||||
pkl_internal_close(pexec->graal_isolatethread);
|
graal_tear_down_isolate(isolateThread);
|
||||||
#ifdef _WIN32
|
|
||||||
DeleteCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_destroy(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to destroy mutex.\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
free(pexec);
|
free(pexec);
|
||||||
*exec = NULL;
|
*exec = NULL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pkl_internal_server_start(pexec->graal_isolatethread);
|
pkl_internal_server_start(isolateThread);
|
||||||
|
|
||||||
pkl_is_initialized = 1;
|
|
||||||
*exec = pexec;
|
*exec = pexec;
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = NULL;
|
error->message = NULL;
|
||||||
@@ -269,8 +174,8 @@ int pkl_init(pkl_message_response_handler handler, void *userData,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pkl_send_message(pkl_exec_t *pexec, unsigned int length, char *message,
|
int pkl_send_message(const pkl_exec_t *pexec, const unsigned int length,
|
||||||
pkl_error_t *error) {
|
char *message, pkl_error_t *error) {
|
||||||
if (pexec == NULL) {
|
if (pexec == NULL) {
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = "pexec is null";
|
error->message = "pexec is null";
|
||||||
@@ -284,14 +189,19 @@ int pkl_send_message(pkl_exec_t *pexec, unsigned int length, char *message,
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lock_response = pkl_lock_mutex(pexec, error);
|
graal_isolatethread_t *thread = graal_get_current_thread(pexec->isolate);
|
||||||
if (lock_response != 0) {
|
if (thread != pexec->isolateThread) {
|
||||||
return lock_response;
|
if (error != NULL) {
|
||||||
|
error->message =
|
||||||
|
"called into pkl_send_message from different thread";
|
||||||
|
}
|
||||||
|
return PKL_ERR_THREAD;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *errormessage = NULL;
|
char *errormessage = NULL;
|
||||||
int resp = pkl_internal_send_message(pexec->graal_isolatethread, length,
|
const int resp = pkl_internal_send_message(thread, (int) length, message,
|
||||||
message, &errormessage);
|
&errormessage);
|
||||||
pkl_unlock_mutex(pexec);
|
|
||||||
if (resp != 0) {
|
if (resp != 0) {
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = errormessage;
|
error->message = errormessage;
|
||||||
@@ -312,35 +222,24 @@ int pkl_close(pkl_exec_t *pexec, pkl_error_t *error) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
graal_isolatethread_t *thread = graal_get_current_thread(pexec->isolate);
|
||||||
EnterCriticalSection(&pexec->mutex);
|
if (thread != pexec->isolateThread) {
|
||||||
#else
|
|
||||||
if (pthread_mutex_lock(&pexec->mutex) != 0) {
|
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = "failed to lock mutex";
|
error->message = "called into pkl_close from different thread";
|
||||||
}
|
}
|
||||||
return PKL_ERR_LOCK;
|
return PKL_ERR_THREAD;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
pkl_runtime_cleanup(pexec);
|
// pkl_internal_server_stop unblocks queue_thread's pending/next poll call, so the join
|
||||||
|
// below is guaranteed to return; only then is it safe to tear down the isolate.
|
||||||
|
pkl_internal_server_stop(thread);
|
||||||
|
pkl_stop_dispatch_worker(pexec);
|
||||||
|
|
||||||
#ifdef _WIN32
|
if (graal_tear_down_isolate(thread) != 0) {
|
||||||
LeaveCriticalSection(&pexec->mutex);
|
fprintf(stderr, "fatal: failed to tear down graal isolate.\n");
|
||||||
DeleteCriticalSection(&pexec->mutex);
|
|
||||||
#else
|
|
||||||
if (pthread_mutex_unlock(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to unlock mutex.\n");
|
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pthread_mutex_destroy(&pexec->mutex) != 0) {
|
|
||||||
fprintf(stderr, "fatal: failed to destroy mutex.\n");
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pkl_is_initialized = 0;
|
|
||||||
free(pexec);
|
free(pexec);
|
||||||
if (error != NULL) {
|
if (error != NULL) {
|
||||||
error->message = NULL;
|
error->message = NULL;
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ public class LibPklInternal {
|
|||||||
|
|
||||||
private LibPklInternal() {}
|
private LibPklInternal() {}
|
||||||
|
|
||||||
@CEntryPoint(name = "pkl_internal_init", builtin = CEntryPoint.Builtin.CREATE_ISOLATE)
|
|
||||||
static native IsolateThread pklInternalInit();
|
|
||||||
|
|
||||||
@CEntryPoint(name = "pkl_internal_send_message")
|
@CEntryPoint(name = "pkl_internal_send_message")
|
||||||
public static int pklInternalSendMessage(
|
public static int pklInternalSendMessage(
|
||||||
IsolateThread thread, int length, CCharPointer ptr, CCharPointerPointer errorMessage) {
|
IsolateThread thread, int length, CCharPointer ptr, CCharPointerPointer errorMessage) {
|
||||||
@@ -64,9 +61,6 @@ public class LibPklInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CEntryPoint(name = "pkl_internal_close", builtin = CEntryPoint.Builtin.TEAR_DOWN_ISOLATE)
|
|
||||||
public static native void pklInternalClose(IsolateThread thread);
|
|
||||||
|
|
||||||
@CEntryPoint(name = "pkl_internal_server_start")
|
@CEntryPoint(name = "pkl_internal_server_start")
|
||||||
public static void pklInternalServerStart(IsolateThread thread) {
|
public static void pklInternalServerStart(IsolateThread thread) {
|
||||||
server = new Server(transport);
|
server = new Server(transport);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class NativeTransport extends AbstractMessageTransport {
|
|||||||
protected void doClose() {}
|
protected void doClose() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doSend(Message message) {
|
protected void doSend(Message message) throws ProtocolException {
|
||||||
try (var os = new ByteArrayOutputStream();
|
try (var os = new ByteArrayOutputStream();
|
||||||
var packer = MessagePack.newDefaultPacker(os)) {
|
var packer = MessagePack.newDefaultPacker(os)) {
|
||||||
var encoder = new ServerMessagePackEncoder(packer);
|
var encoder = new ServerMessagePackEncoder(packer);
|
||||||
@@ -53,9 +53,9 @@ public class NativeTransport extends AbstractMessageTransport {
|
|||||||
// impossible; no IO happens during packing
|
// impossible; no IO happens during packing
|
||||||
throw PklBugException.unreachableCode();
|
throw PklBugException.unreachableCode();
|
||||||
} catch (ProtocolException e) {
|
} catch (ProtocolException e) {
|
||||||
System.err.println("Received unexpected ProtocolException when encoding a message, aborting");
|
// should never happen; messages coming from Pkl should always be well-formed.
|
||||||
e.printStackTrace(System.err);
|
log("Unexpected protocol exception: " + e);
|
||||||
System.exit(1);
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,20 @@ void test_empty_message() {
|
|||||||
printf("✓ Empty message rejected\n");
|
printf("✓ Empty message rejected\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_garbage_send_message() {
|
||||||
|
pkl_error_t err = { 0 };
|
||||||
|
pkl_exec_t *exec = NULL;
|
||||||
|
assert(pkl_init(test_message_handler, NULL, &exec, &err) == 0);
|
||||||
|
|
||||||
|
char *message = (char*) (unsigned char[] ) { 0x41, 0x42, 0x43 };
|
||||||
|
int result = pkl_send_message(exec, 3, message, &err);
|
||||||
|
assert(result == PKL_ERR_PROTOCOL);
|
||||||
|
|
||||||
|
result = pkl_close(exec, &err);
|
||||||
|
assert(result == 0);
|
||||||
|
printf("✓ Garbage message becomes protocol exception\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
printf("Running libpkl C tests...\n\n");
|
printf("Running libpkl C tests...\n\n");
|
||||||
|
|
||||||
@@ -84,6 +98,7 @@ int main() {
|
|||||||
test_init_close();
|
test_init_close();
|
||||||
test_null_send_message();
|
test_null_send_message();
|
||||||
test_empty_message();
|
test_empty_message();
|
||||||
|
test_garbage_send_message();
|
||||||
|
|
||||||
printf("\nAll tests passed!\n");
|
printf("\nAll tests passed!\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -13,17 +13,20 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
@file:Suppress("FunctionName")
|
||||||
|
|
||||||
package org.pkl.libpkl
|
package org.pkl.libpkl
|
||||||
|
|
||||||
import com.sun.jna.Pointer
|
import com.sun.jna.Pointer
|
||||||
import com.sun.jna.ptr.PointerByReference
|
import com.sun.jna.ptr.PointerByReference
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.pkl.core.Release
|
import org.pkl.core.Release
|
||||||
|
|
||||||
class LibPklTest {
|
class LibPklTest {
|
||||||
@Test
|
@Test
|
||||||
fun testMalformedMessage() {
|
fun `malformed message`() {
|
||||||
val messageResponseHandler =
|
val messageResponseHandler =
|
||||||
object : LibPklJNA.PklMessageResponseHandler {
|
object : LibPklJNA.PklMessageResponseHandler {
|
||||||
override fun invoke(length: Int, message: Pointer, userData: Pointer?) {}
|
override fun invoke(length: Int, message: Pointer, userData: Pointer?) {}
|
||||||
@@ -45,7 +48,44 @@ class LibPklTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEmptyMessageIsRejected() {
|
fun `empty message is rejected`() {
|
||||||
|
val exec = init()
|
||||||
|
val error = LibPklJNA.PklError()
|
||||||
|
try {
|
||||||
|
val result = LibPklJNA.INSTANCE.pkl_send_message(exec, 0, byteArrayOf(0), error)
|
||||||
|
assertThat(result).isEqualTo(2)
|
||||||
|
assertThat(error.message).contains("Unexpected end of input")
|
||||||
|
} finally {
|
||||||
|
assertThat(LibPklJNA.INSTANCE.pkl_close(exec, error)).isEqualTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `version string matches current version`() {
|
||||||
|
val currentVersion = Release.current().version.toString()
|
||||||
|
assertThat(LibPklJNA.INSTANCE.pkl_version()).isEqualTo(currentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `calling into libpkl from different thread using same pkl_exec_t instance fails`() {
|
||||||
|
val exec = init()
|
||||||
|
val error = LibPklJNA.PklError()
|
||||||
|
val executor = Executors.newSingleThreadExecutor()
|
||||||
|
try {
|
||||||
|
executor
|
||||||
|
.submit {
|
||||||
|
val resp = LibPklJNA.INSTANCE.pkl_close(exec, error)
|
||||||
|
assertThat(resp).isEqualTo(1)
|
||||||
|
assertThat(error.message).isEqualTo("called into pkl_close from different thread")
|
||||||
|
}
|
||||||
|
.get()
|
||||||
|
} finally {
|
||||||
|
executor.shutdown()
|
||||||
|
assertThat(LibPklJNA.INSTANCE.pkl_close(exec, error)).isEqualTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init(): Pointer {
|
||||||
val execRef = PointerByReference()
|
val execRef = PointerByReference()
|
||||||
val error = LibPklJNA.PklError()
|
val error = LibPklJNA.PklError()
|
||||||
val messageResponseHandler =
|
val messageResponseHandler =
|
||||||
@@ -55,18 +95,6 @@ class LibPklTest {
|
|||||||
assertThat(LibPklJNA.INSTANCE.pkl_init(messageResponseHandler, Pointer.NULL, execRef, error))
|
assertThat(LibPklJNA.INSTANCE.pkl_init(messageResponseHandler, Pointer.NULL, execRef, error))
|
||||||
.`as` { "Failed to call pkl_init: ${error.message}" }
|
.`as` { "Failed to call pkl_init: ${error.message}" }
|
||||||
.isEqualTo(0)
|
.isEqualTo(0)
|
||||||
try {
|
return execRef.value
|
||||||
val result = LibPklJNA.INSTANCE.pkl_send_message(execRef.value, 0, byteArrayOf(0), error)
|
|
||||||
assertThat(result).isEqualTo(2)
|
|
||||||
assertThat(error.message).contains("Unexpected end of input")
|
|
||||||
} finally {
|
|
||||||
assertThat(LibPklJNA.INSTANCE.pkl_close(execRef.value, error)).isEqualTo(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testVersionString() {
|
|
||||||
val currentVersion = Release.current().version.toString()
|
|
||||||
assertThat(LibPklJNA.INSTANCE.pkl_version()).isEqualTo(currentVersion)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,9 @@ class Server(private val transport: MessageTransport) : AutoCloseable {
|
|||||||
build()
|
build()
|
||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
throw ProtocolException(e.message ?: "Failed to create an evalutor. $e", e)
|
throw ProtocolException(e.message ?: "Failed to create an evaluator. $e", e)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
throw ProtocolException(e.message ?: "Failed to create an evaluator. $e", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user