Skip to content

Commit

Permalink
Explicit exit from main (#15299)
Browse files Browse the repository at this point in the history
In a MT environment such as proposed in crystal-lang/rfcs#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.
  • Loading branch information
ysbaddaden authored Dec 25, 2024
1 parent d21008e commit c38f4df
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 15 deletions.
6 changes: 3 additions & 3 deletions src/crystal/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
11 changes: 11 additions & 0 deletions src/crystal/system/unix/main.cr
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion src/crystal/system/wasi/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 11 additions & 11 deletions src/crystal/system/win32/wmain.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

0 comments on commit c38f4df

Please sign in to comment.