From c38f4df4c4206e5fa3f18f1ea4877aa42701050b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 25 Dec 2024 23:19:26 +0100 Subject: [PATCH] Explicit exit from main (#15299) In a MT environment such as proposed in https://github.com/crystal-lang/rfcs/pull/2, the main thread's fiber may be resumed by any thread, and it may return which would terminate the program... but it might return from _another thread_ that the process' main thread, which may be unexpected by the OS. This patch instead explicitly exits from `main` and `wmain`. For backward compatibility reasons (win32 `wmain` and wasi `__main_argc_argv` both call `main` andand are documented to do so), the default `main` still returns, but is being replaced for UNIX targets by one that exits. Maybe the OS actual entrypoint could merely call `Crystal.main` instead of `main` and explicitely exit (there wouldn't be a global `main` except for `UNIX`), but this is out of scope for this PR. --- src/crystal/main.cr | 6 +++--- src/crystal/system/unix/main.cr | 11 +++++++++++ src/crystal/system/wasi/main.cr | 3 ++- src/crystal/system/win32/wmain.cr | 22 +++++++++++----------- 4 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/crystal/system/unix/main.cr diff --git a/src/crystal/main.cr b/src/crystal/main.cr index 625238229c58..9b4384f16a8c 100644 --- a/src/crystal/main.cr +++ b/src/crystal/main.cr @@ -132,8 +132,8 @@ end {% if flag?(:win32) %} require "./system/win32/wmain" -{% end %} - -{% if flag?(:wasi) %} +{% elsif flag?(:wasi) %} require "./system/wasi/main" +{% else %} + require "./system/unix/main" {% end %} diff --git a/src/crystal/system/unix/main.cr b/src/crystal/system/unix/main.cr new file mode 100644 index 000000000000..1592a6342002 --- /dev/null +++ b/src/crystal/system/unix/main.cr @@ -0,0 +1,11 @@ +require "c/stdlib" + +# Prefer explicit exit over returning the status, so we are free to resume the +# main thread's fiber on any thread, without occuring a weird behavior where +# another thread returns from main when the caller might expect the main thread +# to be the one returning. + +fun main(argc : Int32, argv : UInt8**) : Int32 + status = Crystal.main(argc, argv) + LibC.exit(status) +end diff --git a/src/crystal/system/wasi/main.cr b/src/crystal/system/wasi/main.cr index 57ffd5f3f43c..9a3394809271 100644 --- a/src/crystal/system/wasi/main.cr +++ b/src/crystal/system/wasi/main.cr @@ -27,7 +27,8 @@ fun _start LibWasi.proc_exit(status) if status != 0 end -# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program arguments. +# `__main_argc_argv` is called by wasi-libc's `__main_void` with the program +# arguments. fun __main_argc_argv(argc : Int32, argv : UInt8**) : Int32 main(argc, argv) end diff --git a/src/crystal/system/win32/wmain.cr b/src/crystal/system/win32/wmain.cr index 2120bfc06bfc..b1726f90329b 100644 --- a/src/crystal/system/win32/wmain.cr +++ b/src/crystal/system/win32/wmain.cr @@ -12,17 +12,13 @@ require "c/stdlib" lib LibCrystalMain end -# The actual entry point for Windows executables. This is necessary because -# *argv* (and Win32's `GetCommandLineA`) mistranslate non-ASCII characters to -# Windows-1252, so `PROGRAM_NAME` and `ARGV` would be garbled; to avoid that, we -# use this Windows-exclusive entry point which contains the correctly encoded -# UTF-16 *argv*, convert it to UTF-8, and then forward it to the original -# `main`. +# The actual entry point for Windows executables. # -# The different main functions in `src/crystal/main.cr` need not be aware that -# such an alternate entry point exists, nor that the original command line was -# not UTF-8. Thus all other aspects of program initialization still occur there, -# and uses of those main functions continue to work across platforms. +# This is necessary because *argv* (and Win32's `GetCommandLineA`) mistranslate +# non-ASCII characters to Windows-1252, so `PROGRAM_NAME` and `ARGV` would be +# garbled; to avoid that, we use this Windows-exclusive entry point which +# contains the correctly encoded UTF-16 *argv*, convert it to UTF-8, and then +# forward it to the original `main`. # # NOTE: we cannot use anything from the standard library here, including the GC. fun wmain(argc : Int32, argv : UInt16**) : Int32 @@ -46,5 +42,9 @@ fun wmain(argc : Int32, argv : UInt16**) : Int32 end LibC.free(utf8_argv) - status + # prefer explicit exit over returning the status, so we are free to resume the + # main thread's fiber on any thread, without occuring a weird behavior where + # another thread returns from main when the caller might expect the main + # thread to be the one returning. + LibC.exit(status) end