diff --git a/includes/RtsAPI.h b/includes/RtsAPI.h
index 4748060deea161ce1d1c8de97fe596213095f90a..0e29c63b54a8834757bf9588bfb42847282fecd6 100644
--- a/includes/RtsAPI.h
+++ b/includes/RtsAPI.h
@@ -245,6 +245,10 @@ void rts_evalIO (/* inout */ Capability **,
                  /* in    */ HaskellObj p,
                  /* out */   HaskellObj *ret);
 
+void rts_evalStableIOMain (/* inout */ Capability **,
+                           /* in    */ HsStablePtr s,
+                           /* out */   HsStablePtr *ret);
+
 void rts_evalStableIO (/* inout */ Capability **,
                        /* in    */ HsStablePtr s,
                        /* out */   HsStablePtr *ret);
diff --git a/rts/Prelude.h b/rts/Prelude.h
index ae1e9cb2662427955a723ec30520b515d5248527..444aa469ab27b50868f5d6e3017579cda397b4e7 100644
--- a/rts/Prelude.h
+++ b/rts/Prelude.h
@@ -51,6 +51,7 @@ PRELUDE_CLOSURE(base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure);
 PRELUDE_CLOSURE(base_GHCziConcziSignal_runHandlersPtr_closure);
 
 PRELUDE_CLOSURE(base_GHCziTopHandler_flushStdHandles_closure);
+PRELUDE_CLOSURE(base_GHCziTopHandler_runMainIO_closure);
 
 PRELUDE_INFO(ghczmprim_GHCziTypes_Czh_static_info);
 PRELUDE_INFO(ghczmprim_GHCziTypes_Izh_static_info);
@@ -99,6 +100,7 @@ PRELUDE_INFO(base_GHCziStable_StablePtr_con_info);
 #define runHandlersPtr_closure       DLL_IMPORT_DATA_REF(base_GHCziConcziSignal_runHandlersPtr_closure)
 
 #define flushStdHandles_closure   DLL_IMPORT_DATA_REF(base_GHCziTopHandler_flushStdHandles_closure)
+#define runMainIO_closure   DLL_IMPORT_DATA_REF(base_GHCziTopHandler_runMainIO_closure)
 
 #define stackOverflow_closure     DLL_IMPORT_DATA_REF(base_GHCziIOziException_stackOverflow_closure)
 #define heapOverflow_closure      DLL_IMPORT_DATA_REF(base_GHCziIOziException_heapOverflow_closure)
diff --git a/rts/RtsAPI.c b/rts/RtsAPI.c
index c64d8af2e4bc0374f846d0fbbde5940a8f76140a..47f6c93942ad0ce81ad4b862525cacbbb64bebee 100644
--- a/rts/RtsAPI.c
+++ b/rts/RtsAPI.c
@@ -459,6 +459,35 @@ void rts_evalIO (/* inout */ Capability **cap,
     scheduleWaitThread(tso,ret,cap);
 }
 
+/*
+ * rts_evalStableIOMain() is suitable for calling main Haskell thread
+ * stored in (StablePtr (IO a)) it calls rts_evalStableIO but wraps
+ * function in GHC.TopHandler.runMainIO that installs top_handlers.
+ * See Trac #12903.
+ */
+void rts_evalStableIOMain(/* inout */ Capability **cap,
+                          /* in    */ HsStablePtr s,
+                          /* out   */ HsStablePtr *ret)
+{
+    StgTSO* tso;
+    StgClosure *p, *r, *w;
+    SchedulerStatus stat;
+
+    p = (StgClosure *)deRefStablePtr(s);
+    w = rts_apply(*cap, &base_GHCziTopHandler_runMainIO_closure, p);
+    tso = createStrictIOThread(*cap, RtsFlags.GcFlags.initialStkSize, w);
+    // async exceptions are always blocked by default in the created
+    // thread.  See #1048.
+    tso->flags |= TSO_BLOCKEX | TSO_INTERRUPTIBLE;
+    scheduleWaitThread(tso,&r,cap);
+    stat = rts_getSchedStatus(*cap);
+
+    if (stat == Success && ret != NULL) {
+        ASSERT(r != NULL);
+        *ret = getStablePtr((StgPtr)r);
+    }
+}
+
 /*
  * rts_evalStableIO() is suitable for calling from Haskell.  It
  * evaluates a value of the form (StablePtr (IO a)), forcing the
diff --git a/rts/RtsSymbols.c b/rts/RtsSymbols.c
index fec5cfc0564fbe78889e2096c3b2c62fd417539c..44b6591c35d58aaf233c1ef4dcc3703d289df584 100644
--- a/rts/RtsSymbols.c
+++ b/rts/RtsSymbols.c
@@ -647,6 +647,7 @@
       SymI_HasProto(rts_eval)                                           \
       SymI_HasProto(rts_evalIO)                                         \
       SymI_HasProto(rts_evalLazyIO)                                     \
+      SymI_HasProto(rts_evalStableIOMain)                               \
       SymI_HasProto(rts_evalStableIO)                                   \
       SymI_HasProto(rts_eval_)                                          \
       SymI_HasProto(rts_getBool)                                        \
diff --git a/rts/Schedule.c b/rts/Schedule.c
index 1f42e42417c95e9e455c99d35581b4db45f7f18a..33599d0abb7e6d52e2c9938a57f2477fa220c542 100644
--- a/rts/Schedule.c
+++ b/rts/Schedule.c
@@ -2078,7 +2078,10 @@ forkProcess(HsStablePtr *entry
         ioManagerStartCap(&cap);
 #endif
 
-        rts_evalStableIO(&cap, entry, NULL);  // run the action
+        // Install toplevel exception handlers, so interruption
+        // signal will be sent to the main thread.
+        // See Trac #12903
+        rts_evalStableIOMain(&cap, entry, NULL);  // run the action
         rts_checkSchedStatus("forkProcess",cap);
 
         rts_unlock(cap);
diff --git a/rts/package.conf.in b/rts/package.conf.in
index c0256bb028d9efe22503ef718d86eb3bea2ae50a..e328be7b613be58296bec8c5cdd38403f7880984 100644
--- a/rts/package.conf.in
+++ b/rts/package.conf.in
@@ -106,6 +106,7 @@ ld-options:
          , "-Wl,-u,_base_GHCziTopHandler_flushStdHandles_closure"
          , "-Wl,-u,_base_GHCziTopHandler_runIO_closure"
          , "-Wl,-u,_base_GHCziTopHandler_runNonIO_closure"
+         , "-Wl,-u,_base_GHCziTopHandler_runMainIO_closure"
          , "-Wl,-u,_base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
          , "-Wl,-u,_base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
          , "-Wl,-u,_base_GHCziConcziSync_runSparks_closure"
@@ -148,6 +149,7 @@ ld-options:
          , "-Wl,-u,base_GHCziTopHandler_flushStdHandles_closure"
          , "-Wl,-u,base_GHCziTopHandler_runIO_closure"
          , "-Wl,-u,base_GHCziTopHandler_runNonIO_closure"
+         , "-Wl,-u,base_GHCziTopHandler_runMainIO_closure"
          , "-Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
          , "-Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
          , "-Wl,-u,base_GHCziConcziSync_runSparks_closure"
diff --git a/testsuite/tests/rts/T12903.hs b/testsuite/tests/rts/T12903.hs
new file mode 100644
index 0000000000000000000000000000000000000000..ddaf8b97e8adb771b51c8e78d7c921d47a7a8b04
--- /dev/null
+++ b/testsuite/tests/rts/T12903.hs
@@ -0,0 +1,10 @@
+import Control.Concurrent
+import Control.Exception
+import System.Posix
+
+main = do
+  pid <- forkProcess $ do
+           handle (\UserInterrupt{} -> putStrLn "caught")
+                  $ threadDelay 2000000
+  signalProcess sigINT pid
+  threadDelay 2000000
diff --git a/testsuite/tests/rts/T12903.stdout b/testsuite/tests/rts/T12903.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..cad99e12229a1283e99e507e864f2ed1b7fe93c5
--- /dev/null
+++ b/testsuite/tests/rts/T12903.stdout
@@ -0,0 +1 @@
+caught
diff --git a/testsuite/tests/rts/all.T b/testsuite/tests/rts/all.T
index f7d518c311032fb6bc39ac2e799da5b54ee1e5b5..d889276f1ec6c90e974ad075061f91933cb22f36 100644
--- a/testsuite/tests/rts/all.T
+++ b/testsuite/tests/rts/all.T
@@ -345,3 +345,5 @@ test('T10296b', [only_ways('threaded2')], compile_and_run, [''])
 test('T12497', [ unless(opsys('mingw32'), skip)
                ],
                run_command, ['$MAKE -s --no-print-directory T12497'])
+test('T12903', [ when(opsys('mingw32'), skip)], compile_and_run, [''])
+