-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Add kindaUnlift to find out what happens where #43
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ module Troupe.Process | |
exit, | ||
isProcessAlive, | ||
spawnWithOptions, | ||
kindaUnlift, | ||
SpawnOptions (..), | ||
ThreadAffinity (..), | ||
WithMonitor (..), | ||
|
@@ -46,7 +47,7 @@ module Troupe.Process | |
where | ||
|
||
import Control.Applicative (Alternative, (<|>)) | ||
import Control.Concurrent (throwTo) | ||
import Control.Concurrent (threadDelay, throwTo) | ||
import Control.Concurrent.Async | ||
( async, | ||
asyncThreadId, | ||
|
@@ -100,7 +101,7 @@ import Control.Exception.Safe | |
uninterruptibleMask_, | ||
withException, | ||
) | ||
import Control.Monad (MonadPlus, forM, unless, when) | ||
import Control.Monad (MonadPlus, forever, forM, unless, when) | ||
import Control.Monad.Error.Class (MonadError) | ||
import Control.Monad.Fix (MonadFix) | ||
import Control.Monad.IO.Class (MonadIO, liftIO) | ||
|
@@ -583,17 +584,7 @@ data ThreadAffinity | |
-- 'Troupe.spawn', 'Troupe.spawnLink' and 'Troupe.spawnMonitor' are specialized | ||
-- versions of this function. | ||
spawnWithOptions :: (MonadProcess r m, MonadIO m) => SpawnOptions t -> Process r a -> m t | ||
spawnWithOptions !options process = do | ||
let cb pid = do | ||
when (spawnOptionsLink options) $ | ||
linkSTM pid | ||
case spawnOptionsMonitor options of | ||
WithoutMonitor -> pure pid | ||
WithMonitor -> do | ||
ref <- monitorSTM pid | ||
pure (pid, ref) | ||
|
||
spawnImpl (spawnOptionsAffinity options) cb process | ||
spawnWithOptions options = spawnImpl (spawnOptionsAffinity options) (mkCallback options) | ||
{-# SPECIALIZE spawnWithOptions :: SpawnOptions t -> Process r a -> Process r t #-} | ||
|
||
data SendOptions = SendOptions | ||
|
@@ -719,22 +710,43 @@ after :: | |
after = MatchAfter | ||
{-# INLINE after #-} | ||
|
||
kindaUnlift :: (MonadProcess r m, MonadIO io) => ((ThreadAffinity -> Process r a -> io ()) -> m b) -> m b | ||
kindaUnlift foreignSpawner = do | ||
env <- getProcessEnv | ||
foreignSpawner $ \affinity action -> do | ||
_pid <- spawnImplWith env affinity pure action -- XXX: does it make sense to link/monitor a wrapped process? | ||
-- TODO: spawnImplWith should have a blocking version | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure. Would it make sense for the caller thread to become the monitor of the process, instead of spawning two more threads (one monitor, one to run the effective Not even sure that's the right approach. In a way, I'm bit uncomfortable with how threads would be managed now. Does your There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In general, we don't know. A typical server loop is run handler =
bracket open close \socket ->
forever do
connection <- accept socket
void $! forkIO (handler connection)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, I was thinking: what if we invert things a bit? Basically, instead of spawning a monitor thread (as done in This way, whatever There's one (somewhat major) caveat: we must make sure a single thread is never used for two There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this could be done, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, this is how I see it too. |
||
liftIO . forever $ | ||
dpwiz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
threadDelay 10000000 | ||
|
||
mkCallback :: SpawnOptions r -> ProcessId -> ReaderT (ProcessEnv a) STM r | ||
mkCallback !options pid = do | ||
when (spawnOptionsLink options) $ | ||
linkSTM pid | ||
case spawnOptionsMonitor options of | ||
WithoutMonitor -> pure pid | ||
WithMonitor -> do | ||
ref <- monitorSTM pid | ||
pure (pid, ref) | ||
|
||
spawnImpl :: (MonadProcess r m, MonadIO m) => ThreadAffinity -> (ProcessId -> ReaderT (ProcessEnv r) STM t) -> Process r a -> m t | ||
spawnImpl affinity cb process = do | ||
currentEnv <- getProcessEnv | ||
spawnImplWith currentEnv affinity cb process | ||
|
||
liftIO $ do | ||
processContext <- newProcessContext (processEnvNodeContext currentEnv) | ||
let processEnv = currentEnv {processEnvProcessContext = processContext} | ||
spawnImplWith :: MonadIO m => ProcessEnv r -> ThreadAffinity -> (ProcessId -> ReaderT (ProcessEnv r) STM t) -> Process r a -> m t | ||
spawnImplWith currentEnv affinity cb process = liftIO $ do | ||
processContext <- newProcessContext (processEnvNodeContext currentEnv) | ||
let processEnv = currentEnv {processEnvProcessContext = processContext} | ||
|
||
m <- newEmptyTMVarIO | ||
m <- newEmptyTMVarIO | ||
|
||
bracketOnError | ||
(run currentEnv processEnv m) | ||
uninterruptibleCancel | ||
(wrapup m) | ||
bracketOnError | ||
(run processEnv m) | ||
uninterruptibleCancel | ||
(wrapup m) | ||
where | ||
run currentEnv processEnv m = mask_ $ async $ do | ||
run processEnv m = mask_ $ async $ do | ||
c <- newEmptyTMVarIO | ||
let act restore = atomically (readTMVar c) >>= \() -> restore (runProcess process processEnv) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I believe so. In the current example (
Server.run
),unlift
is used in an infinite loop, so there it may not make sense. However, there might be cases where it's used one-shot, and hence the parentProcess
may very well be interested in the child exiting (throughmonitor
), or getting linked.In a way, I think
link
should almost be the default: with the current approach, it's way too easy to end up with ghost thread/processes, no?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A server process may wish to monitor spawned handler processes, but no new implementation details would be needed for that.