/* Step 4: the macOS shape, and the discriminating test of the whole spike. * * main() is the game: it takes the thread the window needs and runs a loop it * never leaves until the compiler says stop. The OCaml runtime is started on a * pthread that C spawned -- exactly where vendor/agent/flan_agent.c already * puts its listener. * * Two separate claims get tested: * a. caml_startup works on a non-main, C-created thread at all. * b. a *different* C thread, one the runtime never created, can call into * OCaml after caml_c_thread_register(). * (b) is the one that matters for the agent: its listener thread is spawned by * flan_agent_start and would have to be able to reach the compiler. */ #include #include #include #include #include #include #include #include #include static char **g_argv; static const char *g_src = "test/programs/edn.flan"; static atomic_int compiler_up = 0; static atomic_int quit = 0; static pthread_t main_tid; static void nap(long ms) { struct timespec t = { ms / 1000, (ms % 1000) * 1000000L }; nanosleep(&t, NULL); } static const value *need(const char *n) { const value *f = caml_named_value(n); if (!f) fprintf(stderr, "spike: %s not registered\n", n); return f; } /* The compiler thread: starts the OCaml runtime off the main thread. */ static void *compiler_thread(void *unused) { const value *f; (void)unused; printf(" [compiler thread] is main thread? %s\n", pthread_equal(pthread_self(), main_tid) ? "YES (wrong)" : "no (correct)"); caml_startup(g_argv); printf(" [compiler thread] caml_startup returned off the main thread\n"); f = need("spike_domains"); if (f) printf(" [compiler thread] %s\n", String_val(caml_callback(*f, Val_unit))); f = need("spike_thread_compile"); if (f) printf(" [compiler thread] %s\n", String_val(caml_callback(*f, caml_copy_string(g_src)))); /* Hand the runtime over so another C thread can borrow it, and prove the main loop kept running throughout. */ atomic_store(&compiler_up, 1); caml_release_runtime_system(); nap(300); caml_acquire_runtime_system(); atomic_store(&quit, 1); return NULL; } /* A second C thread, like the agent's listener: never created by OCaml. */ static void *listener_thread(void *unused) { const value *f; (void)unused; while (!atomic_load(&compiler_up)) nap(5); if (caml_c_thread_register() == 0) { printf(" [listener thread] caml_c_thread_register FAILED\n"); return NULL; } caml_acquire_runtime_system(); f = need("spike_thread_compile"); if (f) printf(" [listener thread] %s\n", String_val(caml_callback(*f, caml_copy_string(g_src)))); caml_release_runtime_system(); caml_c_thread_unregister(); printf(" [listener thread] registered, called OCaml, unregistered\n"); return NULL; } int main(int argc, char **argv) { pthread_t comp, lst; long frames = 0; g_argv = argv; if (argc > 1) g_src = argv[1]; main_tid = pthread_self(); if (pthread_create(&comp, NULL, compiler_thread, NULL) != 0) return 1; if (pthread_create(&lst, NULL, listener_thread, NULL) != 0) return 1; /* The game loop. This thread never calls into OCaml and never blocks on it -- it is the window's thread, and on macOS it has to be this one. */ while (!atomic_load(&quit)) { frames++; nap(1); } pthread_join(comp, NULL); pthread_join(lst, NULL); printf(" [main thread] ran %ld frames without ever entering OCaml\n", frames); printf("spike: the game kept the main thread\n"); return 0; }