From afe463905d9f4102f9e401832f8bbee52973dc08 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Thu, 7 May 2026 19:41:36 +0200 Subject: [PATCH 1/2] Bootstrap Env with generated init The RTS bootstrap filled Env fields directly in C, which made the Env actor hard to extend from Acton. Allocate Env in C only far enough to pin it to the main worker. Then send a bootstrap message that runs the generated Env.__init__ before starting the root actor. Keep the DB state shortcut after Env init so the initialized actor state is persisted before root startup. --- base/builtin/env.c | 9 +---- base/builtin/env.h | 2 +- base/rts/rts.c | 76 +++++++++++++++++++++++++++++++++++----- base/src/__builtin__.act | 4 +-- compiler/acton/test.hs | 3 ++ test/rts/env_init.act | 6 ++++ 6 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 test/rts/env_init.act diff --git a/base/builtin/env.c b/base/builtin/env.c index f0fa24485..d338ff60f 100644 --- a/base/builtin/env.c +++ b/base/builtin/env.c @@ -102,15 +102,9 @@ void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { } -B_Env B_EnvG_newactor(B_WorldCap wc, B_SysCap sc, B_list args) { +B_Env B_EnvG_newactor() { B_Env $tmp = $NEWACTOR(B_Env); - $tmp->cap = wc; - $tmp->args = args; - $tmp->syscap = sc; - $tmp->auth = $tmp->cap; - $tmp->argv = $tmp->args; $tmp->$affinity = 0; // hard-coded to special worker on the main thread - serialize_state_shortcut(($Actor)$tmp); return $tmp; } @@ -137,4 +131,3 @@ B_WorldCap B_WorldCapG_new() { B_NoneType B_WorldCapD___init__ (B_WorldCap self) { return B_None; } - diff --git a/base/builtin/env.h b/base/builtin/env.h index 990fee396..aaad6c4e9 100644 --- a/base/builtin/env.h +++ b/base/builtin/env.h @@ -1,6 +1,6 @@ #pragma once -B_Env B_EnvG_newactor (B_WorldCap, B_SysCap, B_list); +B_Env B_EnvG_newactor (); B_SysCap B_SysCapG_new(); B_NoneType B_SysCapD___init__ (B_SysCap self); diff --git a/base/rts/rts.c b/base/rts/rts.c index 1ac8d2d4f..a3c919ddc 100644 --- a/base/rts/rts.c +++ b/base/rts/rts.c @@ -719,6 +719,7 @@ struct $Cont $Fail$instance = { &$FailG_methods }; //////////////////////////////////////////////////////////////////////////////////////// + $R $InitRootD___call__ ($Cont $this, $WORD val) { typedef $R(*ROOT__init__t)($Actor, $Cont, B_Env); // Assumed type of the ROOT actor's __init__ method return ((ROOT__init__t)root_actor->$class->__init__)(root_actor, ($Cont)val, env_actor); @@ -739,6 +740,55 @@ struct $ContG_class $InitRootG_methods = { struct $Cont $InitRoot$cont = { &$InitRootG_methods }; + +$R $StartRootD___call__ ($Cont $this, $WORD val) { + // Env.__init__ has completed here; now preserve Env and start root init. + serialize_state_shortcut(($Actor)env_actor); + $ASYNC(root_actor, &$InitRoot$cont); + return $R_DONE(val); +} + +struct $ContG_class $StartRootG_methods = { + "$StartRoot", + UNASSIGNED, + NULL, + $ContD___init__, + $ContD___serialize__, + $ContD___deserialize__, + $ContD___bool__, + $ContD___str__, + $ContD___str__, + $StartRootD___call__ +}; +struct $Cont $StartRoot$cont = { + &$StartRootG_methods +}; + +$R $InitEnvD___call__ ($Cont $this, $WORD val) { + typedef $R(*ENV__init__t)(B_Env, $Cont, B_WorldCap, B_SysCap, B_list, B_int); + B_tuple init = (B_tuple)val; + B_WorldCap wc = (B_WorldCap)init->components[0]; + B_SysCap sc = (B_SysCap)init->components[1]; + B_list args = (B_list)init->components[2]; + B_int nr_wthreads = (B_int)init->components[3]; + return ((ENV__init__t)env_actor->$class->__init__)(env_actor, &$StartRoot$cont, wc, sc, args, nr_wthreads); +} + +struct $ContG_class $InitEnvG_methods = { + "$InitEnv", + UNASSIGNED, + NULL, + $ContD___init__, + $ContD___serialize__, + $ContD___deserialize__, + $ContD___bool__, + $ContD___str__, + $ContD___str__, + $InitEnvD___call__ +}; +struct $Cont $InitEnv$cont = { + &$InitEnvG_methods +}; //////////////////////////////////////////////////////////////////////////////////////// #ifdef ACTON_DB @@ -1402,25 +1452,31 @@ void BOOTSTRAP(int argc, char *argv[]) { for (int i=0; i< argc; i++) wit->$class->append(wit,args,to$str(argv[i])); - env_actor = B_EnvG_newactor(B_WorldCapG_new(), B_SysCapG_new(), args); - env_actor->nr_wthreads = toB_int(num_wthreads); - + env_actor = B_EnvG_newactor(); root_actor = $ROOT(); // Assumed to return $NEWACTOR(X) for the selected root actor X + + // Bootstrap targets Env first. $InitEnv runs Env.__init__ with + // $StartRoot$cont, which schedules root init after Env is initialized. + B_tuple env_init = $NEWTUPLE(4, + B_WorldCapG_new(), + B_SysCapG_new(), + args, + toB_int(num_wthreads)); time_t now = current_time(); - B_Msg m = B_MsgG_newXX(root_actor, &$InitRoot$cont, now, &$Done$instance); + B_Msg m = B_MsgG_newXX(($Actor)env_actor, &$InitEnv$cont, now, env_init); #ifdef ACTON_DB if (db) { int ret = 0, minority_status = 0; while(!rts_exit) { - ret = remote_enqueue_in_txn(($WORD*)&m->$globkey, 1, NULL, 0, MSG_QUEUE, (WORD)root_actor->$globkey, &minority_status, NULL, db); - rtsd_printf(" # enqueue bootstrap msg %ld to root actor queue %ld returns %d, minority_status %d", m->$globkey, root_actor->$globkey, ret, minority_status); - if(!handle_status_and_schema_mismatch(ret, minority_status, root_actor->$globkey)) + ret = remote_enqueue_in_txn(($WORD*)&m->$globkey, 1, NULL, 0, MSG_QUEUE, (WORD)env_actor->$globkey, &minority_status, NULL, db); + rtsd_printf(" # enqueue bootstrap msg %ld to env actor queue %ld returns %d, minority_status %d", m->$globkey, env_actor->$globkey, ret, minority_status); + if(!handle_status_and_schema_mismatch(ret, minority_status, env_actor->$globkey)) break; } } #endif - if (ENQ_msg(m, root_actor)) { - ENQ_ready(root_actor); + if (ENQ_msg(m, ($Actor)env_actor)) { + ENQ_ready(($Actor)env_actor); } } @@ -1774,6 +1830,8 @@ void $register_rts () { $register(&$DoneG_methods); $register(&$InitRootG_methods); $register(&B_EnvG_methods); + $register(&$StartRootG_methods); + $register(&$InitEnvG_methods); } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/base/src/__builtin__.act b/base/src/__builtin__.act index 2c328f917..f9676bafc 100644 --- a/base/src/__builtin__.act +++ b/base/src/__builtin__.act @@ -1122,12 +1122,12 @@ class SysCap(): """ pass -actor Env (wc: WorldCap, sc: SysCap, args: list[str]): +actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int): cap = wc auth = wc syscap = sc argv = args - nr_wthreads: int = 0 + nr_wthreads = wthreads action def getenv(name: str) -> ?str: """Get the value of an environment variable""" diff --git a/compiler/acton/test.hs b/compiler/acton/test.hs index bee2518de..697fd07fc 100644 --- a/compiler/acton/test.hs +++ b/compiler/acton/test.hs @@ -1129,6 +1129,9 @@ rtsTests = (returnCode, cmdOut, cmdErr) <- runThing "--rts-wthreads" "../../test/rts/argv7.act" assertEqual "RTS wthreads error retCode" (ExitFailure 1) returnCode assertEqual "RTS wthreads error cmdErr" "ERROR: --rts-wthreads requires an argument.\n" cmdErr + + , testCase "Env init starts root actor with initialized Env" $ do + testBuildAndRun "" "--rts-wthreads=7" ExitSuccess False "../../test/rts/env_init.act" ] stdlibTests = diff --git a/test/rts/env_init.act b/test/rts/env_init.act new file mode 100644 index 000000000..490ea693a --- /dev/null +++ b/test/rts/env_init.act @@ -0,0 +1,6 @@ +actor main(env): + # main only runs if the Env bootstrap continuation starts root init. + if env.nr_wthreads != 7: + env.exit(1) + else: + env.exit(0) From f2a745885f11cc61ef51f6cec725ff582c5eb8e1 Mon Sep 17 00:00:00 2001 From: Kristian Larsson Date: Thu, 7 May 2026 20:12:15 +0200 Subject: [PATCH 2/2] Move Env services into actors Generated Env init creates EnvVar and Stdio service actors. C bootstrap passes EnvCap into generated Env init. EnvCap is not stored on Env. User code cannot forge service construction. EnvVar owns process environment access and is exposed as env.vars. Env keeps wrappers for existing getenv/setenv callers. Stdio owns stdin, stdout, and stderr and is exposed as env.stdio. Stdio receives the EnvVar actor for LANG lookup during stdin setup. Passing Env self from Env init is rejected by current actor init rules. --- base/builtin/env.c | 28 ++++++++++-- base/builtin/env.h | 3 ++ base/rts/rts.c | 10 +++-- base/src/__builtin__.act | 68 ++++++++++++++++++++++++++---- base/src/__builtin__.ext.c | 10 ++--- compiler/acton/test.hs | 2 +- test/core_lang_auto/envcap__bf.act | 4 ++ test/rts/env_init.act | 8 ++++ 8 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 test/core_lang_auto/envcap__bf.act diff --git a/base/builtin/env.c b/base/builtin/env.c index d338ff60f..bc79df8d8 100644 --- a/base/builtin/env.c +++ b/base/builtin/env.c @@ -37,14 +37,19 @@ extern char rts_exit; extern int return_val; -// Env ///////////////////////////////////////////////////////////////////////// +// Stdio /////////////////////////////////////////////////////////////////////// -$R B_EnvD_stdout_writeG_local (B_Env self, $Cont c$cont, B_str s) { +$R B_StdioD_outG_local (B_Stdio self, $Cont c$cont, B_str s) { printf("%s", s->str); return $R_CONT(c$cont, B_None); } -$R B_EnvD_set_stdinG_local (B_Env self, $Cont c$cont, B_bool canonical, B_bool echo) { +$R B_StdioD_errG_local (B_Stdio self, $Cont c$cont, B_str s) { + fprintf(stderr, "%s", s->str); + return $R_CONT(c$cont, B_None); +} + +$R B_StdioD_set_stdinG_local (B_Stdio self, $Cont c$cont, B_bool canonical, B_bool echo) { #if defined(_WIN32) || defined(_WIN64) #else struct termios attr; @@ -84,7 +89,7 @@ void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { } } -$R B_EnvD__on_stdin_bytesG_local (B_Env self, $Cont c$cont, $action cb) { +$R B_StdioD__on_stdin_bytesG_local (B_Stdio self, $Cont c$cont, $action cb) { // This should be the only call in env that does IO stuff, so it is safe to // pin affinity here (and not earlier).. pin_actor_affinity(); @@ -95,6 +100,9 @@ void read_stdin(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { return $R_CONT(c$cont, B_None); } + +// Env ///////////////////////////////////////////////////////////////////////// + $R B_EnvD_exitG_local (B_Env self, $Cont c$cont, B_int n) { return_val = fromB_int(n); rts_shutdown(); @@ -131,3 +139,15 @@ B_WorldCap B_WorldCapG_new() { B_NoneType B_WorldCapD___init__ (B_WorldCap self) { return B_None; } + + +B_EnvCap B_EnvCapG_new() { + B_EnvCap $tmp = acton_malloc(sizeof(struct B_EnvCap)); + $tmp->$class = &B_EnvCapG_methods; + // B_EnvCapG_methods.__init__($tmp); + return $tmp; +} + +B_NoneType B_EnvCapD___init__ (B_EnvCap self) { + return B_None; +} diff --git a/base/builtin/env.h b/base/builtin/env.h index aaad6c4e9..e28f087be 100644 --- a/base/builtin/env.h +++ b/base/builtin/env.h @@ -7,3 +7,6 @@ B_NoneType B_SysCapD___init__ (B_SysCap self); B_WorldCap B_WorldCapG_new(); B_NoneType B_WorldCapD___init__ (B_WorldCap self); + +B_EnvCap B_EnvCapG_new(); +B_NoneType B_EnvCapD___init__ (B_EnvCap self); diff --git a/base/rts/rts.c b/base/rts/rts.c index a3c919ddc..48be95cf9 100644 --- a/base/rts/rts.c +++ b/base/rts/rts.c @@ -765,13 +765,14 @@ struct $Cont $StartRoot$cont = { }; $R $InitEnvD___call__ ($Cont $this, $WORD val) { - typedef $R(*ENV__init__t)(B_Env, $Cont, B_WorldCap, B_SysCap, B_list, B_int); + typedef $R(*ENV__init__t)(B_Env, $Cont, B_WorldCap, B_SysCap, B_list, B_int, B_EnvCap); B_tuple init = (B_tuple)val; B_WorldCap wc = (B_WorldCap)init->components[0]; B_SysCap sc = (B_SysCap)init->components[1]; B_list args = (B_list)init->components[2]; B_int nr_wthreads = (B_int)init->components[3]; - return ((ENV__init__t)env_actor->$class->__init__)(env_actor, &$StartRoot$cont, wc, sc, args, nr_wthreads); + B_EnvCap envcap = (B_EnvCap)init->components[4]; + return ((ENV__init__t)env_actor->$class->__init__)(env_actor, &$StartRoot$cont, wc, sc, args, nr_wthreads, envcap); } struct $ContG_class $InitEnvG_methods = { @@ -1457,11 +1458,12 @@ void BOOTSTRAP(int argc, char *argv[]) { // Bootstrap targets Env first. $InitEnv runs Env.__init__ with // $StartRoot$cont, which schedules root init after Env is initialized. - B_tuple env_init = $NEWTUPLE(4, + B_tuple env_init = $NEWTUPLE(5, B_WorldCapG_new(), B_SysCapG_new(), args, - toB_int(num_wthreads)); + toB_int(num_wthreads), + B_EnvCapG_new()); time_t now = current_time(); B_Msg m = B_MsgG_newXX(($Actor)env_actor, &$InitEnv$cont, now, env_init); #ifdef ACTON_DB diff --git a/base/src/__builtin__.act b/base/src/__builtin__.act index f9676bafc..1597c71f3 100644 --- a/base/src/__builtin__.act +++ b/base/src/__builtin__.act @@ -1122,13 +1122,12 @@ class SysCap(): """ pass -actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int): - cap = wc - auth = wc - syscap = sc - argv = args - nr_wthreads = wthreads +class EnvCap(): + """EnvCap authorizes construction of Env-owned service actors. + """ + pass +actor EnvVar(envcap: EnvCap): action def getenv(name: str) -> ?str: """Get the value of an environment variable""" res = getenvb(name.encode()) @@ -1156,7 +1155,11 @@ actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int): """Unset an environment variable""" NotImplemented - action def stdout_write(s: str) -> None: +actor Stdio(envcap: EnvCap, vars: EnvVar): + action def out(s: str) -> None: + NotImplemented + + action def err(s: str) -> None: NotImplemented action def stdin_install(on_stdin: ?action(str) -> None, encoding: ?str=None, on_error: ?action(str, bytes) -> None, on_stdin_bytes: ?action(bytes) -> None) -> None: @@ -1174,7 +1177,7 @@ actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int): # Default to utf-8 if we're unable to discover the encoding encoding = "utf-8" # Read encoding from the LANG environment variable - lang_env = getenv("LANG") + lang_env = vars.getenv("LANG") if lang_env is not None: try: encoding = lang_env.split(".")[1].lower() @@ -1195,5 +1198,54 @@ actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int): action def _on_stdin_bytes(cb: action(bytes) -> None) -> None: NotImplemented +def _new_vars(envcap: EnvCap): + return EnvVar(envcap) + +actor Env (wc: WorldCap, sc: SysCap, args: list[str], wthreads: int, envcap: EnvCap): + cap = wc + auth = wc + syscap = sc + argv = args + nr_wthreads = wthreads + vars = _new_vars(envcap) + + action def getenv(name: str) -> ?str: + """Get the value of an environment variable""" + return vars.getenv(name) + + action def getenvb(name: bytes) -> ?bytes: + """Get the value of an environment variable""" + return vars.getenvb(name) + + action def setenv(n: str, v: str) -> None: + """Set the value of an environment variable""" + vars.setenv(n, v) + + action def setenvb(n: bytes, v: bytes) -> None: + """Set the value of an environment variable""" + vars.setenvb(n, v) + + action def unsetenv(n: str) -> None: + """Unset an environment variable""" + vars.unsetenv(n) + + action def unsetenvb(n: bytes) -> None: + """Unset an environment variable""" + vars.unsetenvb(n) + + stdio = Stdio(envcap, vars) + + action def stdout_write(s: str) -> None: + stdio.out(s) + + action def stdin_install(on_stdin: ?action(str) -> None, encoding: ?str=None, on_error: ?action(str, bytes) -> None, on_stdin_bytes: ?action(bytes) -> None) -> None: + stdio.stdin_install(on_stdin, encoding, on_error, on_stdin_bytes) + + action def set_stdin(canonical: ?bool, echo: ?bool) -> None: + stdio.set_stdin(canonical, echo) + + action def is_tty() -> bool: + return stdio.is_tty() + action def exit(n: int): NotImplemented diff --git a/base/src/__builtin__.ext.c b/base/src/__builtin__.ext.c index 353e44773..22dba533f 100644 --- a/base/src/__builtin__.ext.c +++ b/base/src/__builtin__.ext.c @@ -29,8 +29,8 @@ B_str B_type(B_value a) { return to$str("None"); } -$R B_EnvD_getenvbG_local (B_Env self, $Cont C_cont, B_bytes name) { - // uv_os_getenv is not threadsafe but our Env actor forces serial execution +$R B_EnvVarD_getenvbG_local (B_EnvVar self, $Cont C_cont, B_bytes name) { + // uv_os_getenv is not threadsafe but our EnvVar actor forces serial execution // Try to use a small fixed size buffer size_t len = 256; @@ -55,7 +55,7 @@ B_str B_type(B_value a) { return $R_CONT(C_cont, to$bytes(value)); } -$R B_EnvD_setenvbG_local (B_Env self, $Cont C_cont, B_bytes name, B_bytes value) { +$R B_EnvVarD_setenvbG_local (B_EnvVar self, $Cont C_cont, B_bytes name, B_bytes value) { const char* env_var = fromB_bytes(name); const char* env_val = fromB_bytes(value); int r = uv_os_setenv(env_var, env_val); @@ -65,7 +65,7 @@ B_str B_type(B_value a) { return $R_CONT(C_cont, B_None); } -$R B_EnvD_unsetenvbG_local (B_Env self, $Cont C_cont, B_bytes name) { +$R B_EnvVarD_unsetenvbG_local (B_EnvVar self, $Cont C_cont, B_bytes name) { const char* env_var = fromB_bytes(name); int r = uv_os_unsetenv(env_var); if (r < 0) { @@ -75,7 +75,7 @@ B_str B_type(B_value a) { } // action def is_tty() -> bool: -$R B_EnvD_is_ttyG_local (B_Env self, $Cont C_cont) { +$R B_StdioD_is_ttyG_local (B_Stdio self, $Cont C_cont) { return $R_CONT(C_cont, toB_bool(isatty(1))); } diff --git a/compiler/acton/test.hs b/compiler/acton/test.hs index 697fd07fc..136f938db 100644 --- a/compiler/acton/test.hs +++ b/compiler/acton/test.hs @@ -1130,7 +1130,7 @@ rtsTests = assertEqual "RTS wthreads error retCode" (ExitFailure 1) returnCode assertEqual "RTS wthreads error cmdErr" "ERROR: --rts-wthreads requires an argument.\n" cmdErr - , testCase "Env init starts root actor with initialized Env" $ do + , testCase "Env init starts root actor with initialized Env and service actors" $ do testBuildAndRun "" "--rts-wthreads=7" ExitSuccess False "../../test/rts/env_init.act" ] diff --git a/test/core_lang_auto/envcap__bf.act b/test/core_lang_auto/envcap__bf.act new file mode 100644 index 000000000..f0415b3c3 --- /dev/null +++ b/test/core_lang_auto/envcap__bf.act @@ -0,0 +1,4 @@ +# It should not be possible to forge EnvCap to construct Env-owned services. + +actor main(env): + stdio = Stdio(EnvCap(), env.vars) diff --git a/test/rts/env_init.act b/test/rts/env_init.act index 490ea693a..af4594e78 100644 --- a/test/rts/env_init.act +++ b/test/rts/env_init.act @@ -1,6 +1,14 @@ actor main(env): # main only runs if the Env bootstrap continuation starts root init. + env.vars.setenv("ACTON_ENV_INIT_TEST", "ok") + env.stdio.out("") + env.stdio.err("") if env.nr_wthreads != 7: env.exit(1) + elif env.vars.getenv("ACTON_ENV_INIT_TEST") != "ok": + env.exit(1) + elif env.getenv("ACTON_ENV_INIT_TEST") != "ok": + env.exit(1) else: + env.vars.unsetenv("ACTON_ENV_INIT_TEST") env.exit(0)