diff --git a/src/c.cr b/src/c.cr
index 6c935dfaa1b152007d39343fd019b0ba804282d1..45861e4ff088cc3ac821800b0e72e26f730ecfaa 100644
--- a/src/c.cr
+++ b/src/c.cr
@@ -22,16 +22,6 @@ lib C
   fun memcmp(p1 : Void*, p2 : Void*, size : C::SizeT) : Int32
 end
 
-def exit(status = 0)
-  run_at_exit
-  Process.exit(status)
-end
-
-def abort(message, status = 1)
-  puts message
-  exit status
-end
-
 def sleep(seconds)
   if seconds < 0
     raise ArgumentError.new "sleep seconds must be positive"
diff --git a/src/main.cr b/src/main.cr
index 2918aa47f4b1a285ea0d1bbccec64ad2e4e8dded..7c2acfeb17f272d16c2b7ff2f515d5ffa3a115c5 100644
--- a/src/main.cr
+++ b/src/main.cr
@@ -2,19 +2,38 @@ lib CrystalMain
   fun __crystal_main(argc : Int32, argv : UInt8**)
 end
 
-$at_exit_handlers = nil
+module AtExitHandlers
+  @@handlers = nil
+
+  def self.add(handler)
+    handlers = @@handlers ||= [] of ->
+    handlers << handler
+  end
+
+  def self.run
+    return if @@running
+    @@running = true
+
+    begin
+      @@handlers.try &.each &.call
+    rescue handler_ex
+      puts "Error running at_exit handler: #{handler_ex}"
+    end
+  end
+end
 
 def at_exit(&handler)
-  handlers = $at_exit_handlers ||= [] of ->
-  handlers << handler
+  AtExitHandlers.add(handler)
 end
 
-def run_at_exit
-  begin
-    $at_exit_handlers.try &.each &.call
-  rescue handler_ex
-    puts "Error running at_exit handler: #{handler_ex}"
-  end
+def exit(status = 0)
+  AtExitHandlers.run
+  Process.exit(status)
+end
+
+def abort(message, status = 1)
+  puts message
+  exit status
 end
 
 macro redefine_main(name = main)
@@ -29,7 +48,7 @@ macro redefine_main(name = main)
     end
     1
   ensure
-    run_at_exit
+    AtExitHandlers.run
   end
 end