-
Notifications
You must be signed in to change notification settings - Fork 333
Polysemy concurrency effect #2748
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
Merged
mdimjasevic
merged 10 commits into
wireapp:develop
from
isovector:polysemy-unsafe-concurrency
Oct 5, 2022
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
27b8a35
feat: add unsafe concurrency effect
isovector e84a5c1
feat: just hoist traverse
isovector 5e06ffe
chore: make format
isovector fdda504
feat: add a "safety" flag
isovector dc6ab13
chore: make format
isovector 33d2c2d
refactor: UnsafeConcurrency -> Concurrency
isovector 1d15796
chore: make format
isovector b752a10
feat: add haddocks
isovector 4dfdd6b
doc: changelog
isovector 41afdc4
Hi CI
mdimjasevic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add a Concurrency effect for Polysemy |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
{-# LANGUAGE StandaloneKindSignatures #-} | ||
|
||
module Wire.Sem.Concurrency where | ||
|
||
import Data.Kind (Type) | ||
import Imports | ||
import Polysemy | ||
import Polysemy.Internal | ||
|
||
data ConcurrencySafety = Safe | Unsafe | ||
|
||
-- | Polysemy "effect" for hinting about concurrency. This comes with a host of | ||
-- caveats, because concurrency fundamentally is not an effect we can ascribe | ||
-- any semantics to. | ||
-- | ||
-- For example, what should the result of the following program be? | ||
-- | ||
-- @@ | ||
-- unsafePooledMapConcurrentlyN_ 8 put [0..10] | ||
-- get | ||
-- @@ | ||
-- | ||
-- There is no answer, and the actual behavior depends on unpredictable quirks | ||
-- of the runtime. In general, we have no means of combining the resulting | ||
-- state changes, so we have no option other than to arbitrarily pick one. | ||
-- | ||
-- This is confusing behavior --- especially when the call to `Concurrency` is | ||
-- far away from the observed bug. | ||
-- | ||
-- Notice that almost everything in Polysemy is "stateful", even things that | ||
-- don't invoke 'Polysemy.State.State'. The 'Polysemy.Error.Error' effect also | ||
-- carries itself around as "state", and thus any interpretation composed of | ||
-- these interpretations is subject to dropping observable state changes. | ||
-- | ||
-- There is a "safe" usage of 'Concurrency', at least, no more unsafe than 'IO' | ||
-- when the action you want to perform concurrently requires only @'Final' | ||
-- 'IO'@. This use case is common in interpreters which can statically | ||
-- guarantee their scoped effects do not have access to the full polysemy | ||
-- stack. | ||
type Concurrency :: ConcurrencySafety -> (Type -> Type) -> Type -> Type | ||
data Concurrency (safe :: ConcurrencySafety) m a where | ||
UnsafePooledMapConcurrentlyN :: | ||
Foldable t => | ||
Int -> | ||
(a -> m b) -> | ||
t a -> | ||
Concurrency safe m [b] | ||
UnsafePooledMapConcurrentlyN_ :: | ||
Foldable t => | ||
Int -> | ||
(a -> m b) -> | ||
t a -> | ||
Concurrency safe m () | ||
|
||
unsafePooledMapConcurrentlyN :: | ||
forall r t a b. | ||
(Member (Concurrency 'Unsafe) r, Foldable t) => | ||
-- | Max. number of threads. Should not be less than 1. | ||
Int -> | ||
(a -> Sem r b) -> | ||
t a -> | ||
Sem r [b] | ||
unsafePooledMapConcurrentlyN n f as = | ||
send | ||
( UnsafePooledMapConcurrentlyN n f as :: | ||
Concurrency 'Unsafe (Sem r) [b] | ||
) | ||
{-# INLINEABLE unsafePooledMapConcurrentlyN #-} | ||
|
||
unsafePooledMapConcurrentlyN_ :: | ||
forall r t a b. | ||
(Member (Concurrency 'Unsafe) r, Foldable t) => | ||
-- | Max. number of threads. Should not be less than 1. | ||
Int -> | ||
(a -> Sem r b) -> | ||
t a -> | ||
Sem r () | ||
unsafePooledMapConcurrentlyN_ n f as = | ||
send | ||
(UnsafePooledMapConcurrentlyN_ n f as :: Concurrency 'Unsafe (Sem r) ()) | ||
{-# INLINEABLE unsafePooledMapConcurrentlyN_ #-} | ||
|
||
pooledMapConcurrentlyN :: | ||
forall r' r t a b. | ||
r' ~ '[Final IO] => | ||
(Member (Concurrency 'Safe) r, Subsume r' r, Foldable t) => | ||
-- | Max. number of threads. Should not be less than 1. | ||
Int -> | ||
(a -> Sem r' b) -> | ||
t a -> | ||
Sem r [b] | ||
pooledMapConcurrentlyN n f as = | ||
send | ||
( UnsafePooledMapConcurrentlyN n (subsume_ @r' @r . f) as :: | ||
Concurrency 'Safe (Sem r) [b] | ||
) | ||
{-# INLINEABLE pooledMapConcurrentlyN #-} | ||
|
||
pooledMapConcurrentlyN_ :: | ||
forall r' r t a b. | ||
r' ~ '[Final IO] => | ||
(Member (Concurrency 'Safe) r, Subsume r' r, Foldable t) => | ||
-- | Max. number of threads. Should not be less than 1. | ||
Int -> | ||
(a -> Sem r' b) -> | ||
t a -> | ||
Sem r () | ||
pooledMapConcurrentlyN_ n f as = | ||
send | ||
( UnsafePooledMapConcurrentlyN_ n (subsume_ @r' @r . f) as :: | ||
Concurrency 'Safe (Sem r) () | ||
) | ||
{-# INLINEABLE pooledMapConcurrentlyN_ #-} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
module Wire.Sem.Concurrency.IO where | ||
|
||
import Imports | ||
import Polysemy | ||
import Polysemy.Final | ||
import UnliftIO (pooledMapConcurrentlyN, pooledMapConcurrentlyN_) | ||
import Wire.Sem.Concurrency (Concurrency (..), ConcurrencySafety (Safe)) | ||
|
||
------------------------------------------------------------------------------ | ||
|
||
-- | Safely perform concurrency that wraps only IO effects. | ||
performConcurrency :: | ||
Member (Final IO) r => | ||
Sem (Concurrency 'Safe ': r) a -> | ||
Sem r a | ||
performConcurrency = unsafelyPerformConcurrency | ||
|
||
------------------------------------------------------------------------------ | ||
|
||
-- | VERY UNSAFELY perform concurrency in Polysemy. This is likely to lead to | ||
-- obscure bugs. See the notes on 'Concurrency' to get a better understanding | ||
-- of what can go wrong here. | ||
unsafelyPerformConcurrency :: | ||
Member (Final IO) r => | ||
Sem (Concurrency safe ': r) a -> | ||
Sem r a | ||
unsafelyPerformConcurrency = interpretFinal @IO $ \case | ||
UnsafePooledMapConcurrentlyN n f t -> do | ||
st <- getInitialStateS | ||
faction <- bindS f | ||
let action a = faction $ a <$ st | ||
z <- liftS $ pooledMapConcurrentlyN n action $ toList t | ||
Inspector ins <- getInspectorS | ||
pure $ fmap (fmap (mapMaybe ins)) z | ||
UnsafePooledMapConcurrentlyN_ n f t -> do | ||
st <- getInitialStateS | ||
faction <- bindS f | ||
let action a = faction $ a <$ st | ||
liftS $ pooledMapConcurrentlyN_ n action $ toList t |
19 changes: 19 additions & 0 deletions
19
libs/polysemy-wire-zoo/src/Wire/Sem/Concurrency/Sequential.hs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module Wire.Sem.Concurrency.Sequential where | ||
|
||
import Imports | ||
import Polysemy | ||
import Wire.Sem.Concurrency | ||
|
||
------------------------------------------------------------------------------ | ||
|
||
-- | Safely perform "concurrency" by doing it sequentially. | ||
sequentiallyPerformConcurrency :: Sem (Concurrency safe ': r) a -> Sem r a | ||
sequentiallyPerformConcurrency = interpretH $ \case | ||
UnsafePooledMapConcurrentlyN _ f t -> do | ||
st <- getInitialStateT | ||
ftraverse <- bindT $ traverse @[] f | ||
raise $ sequentiallyPerformConcurrency $ ftraverse $ toList t <$ st | ||
UnsafePooledMapConcurrentlyN_ _ f (t :: t x) -> do | ||
st <- getInitialStateT | ||
ftraverse_ <- bindT $ traverse_ @t f | ||
raise $ sequentiallyPerformConcurrency $ ftraverse_ $ t <$ st |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should this have been
'Safe
instead ofsafe
in the type:: Sem (Concurrency safe ': r) a -> Sem r a
?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.
@isovector , I guess this has slipped your attention because I merged this before you got a chance to reply. Here's just a reminder.