Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Q
quasar
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Jens Nolte
quasar
Commits
951fe87f
Commit
951fe87f
authored
3 years ago
by
Jens Nolte
Browse files
Options
Downloads
Patches
Plain Diff
Write exception-safe withRootResourceManager
parent
4eedb419
No related branches found
Branches containing commit
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
src/Quasar/ResourceManager.hs
+74
-27
74 additions, 27 deletions
src/Quasar/ResourceManager.hs
test/Quasar/ResourceManagerSpec.hs
+12
-11
12 additions, 11 deletions
test/Quasar/ResourceManagerSpec.hs
with
86 additions
and
38 deletions
src/Quasar/ResourceManager.hs
+
74
−
27
View file @
951fe87f
...
...
@@ -21,6 +21,7 @@ module Quasar.ResourceManager (
attachDisposeAction_
,
-- ** Initialization
CombinedException
,
withRootResourceManager
,
CancelLinkedThread
,
...
...
@@ -31,19 +32,20 @@ module Quasar.ResourceManager (
)
where
import
Control.Concurrent
(
ThreadId
,
forkIOWithUnmask
,
myThreadId
,
throwTo
,
forkIO
)
import
Control.Concurrent
(
ThreadId
,
forkIOWithUnmask
,
myThreadId
,
throwTo
)
import
Control.Concurrent.STM
import
Control.Monad.Catch
import
Control.Monad.Reader
import
Data.Foldable
(
toList
)
import
Data.List.NonEmpty
(
NonEmpty
(
..
))
import
Data.Maybe
(
isJust
)
import
Data.List
qualified
as
List
import
Data.List.NonEmpty
(
NonEmpty
(
..
),
nonEmpty
)
import
Data.List.NonEmpty
qualified
as
NonEmpty
import
Data.Sequence
import
Data.Sequence
qualified
as
Seq
import
Quasar.Awaitable
import
Quasar.Disposable
import
Quasar.Prelude
import
System.IO
(
fixIO
,
hPutStrLn
,
stderr
)
import
Quasar.Utils.Concurrent
data
FailedToRegisterResource
=
FailedToRegisterResource
...
...
@@ -180,13 +182,6 @@ captureTask action = do
pure
$
Task
disposable
awaitable
-- * ExceptionHandler
type
ExceptionHandler
=
SomeException
->
IO
()
loggingExceptionHandler
::
ExceptionHandler
loggingExceptionHandler
ex
=
traceIO
$
displayException
ex
-- | A computation bound to a resource manager with 'linkThread' should be canceled.
data
CancelLinkedThread
=
CancelLinkedThread
Unique
...
...
@@ -202,35 +197,87 @@ data LinkState = LinkStateLinked ThreadId | LinkStateThrowing | LinkStateComplet
-- * Resource manager implementations
-- ** Root resource manager
newtype
CombinedException
=
CombinedException
[
SomeException
]
newtype
CombinedException
=
CombinedException
(
NonEmpty
SomeException
)
deriving
stock
Show
instance
Exception
CombinedException
where
displayException
(
CombinedException
exceptions
)
=
intercalate
"
\n
"
(
header
:
exceptionMessages
)
where
header
=
mconcat
[
"CombinedException with "
,
show
(
NonEmpty
.
length
exceptions
),
"exceptions:"
]
exceptionMessages
=
(
displayException
<$>
toList
exceptions
)
data
RootResourceManager
=
RootResourceManager
ResourceManager
ExceptionHandler
data
RootResourceManagerState
=
RootResourceManagerNormal
|
RootResourceManagerDisposing
|
RootResourceManagerDisposed
deriving
stock
Eq
data
RootResourceManager
=
RootResourceManager
ResourceManager
(
TVar
RootResourceManagerState
)
(
TVar
(
Seq
SomeException
))
(
Awaitable
()
)
instance
IsResourceManager
RootResourceManager
where
attachDisposable
(
RootResourceManager
child
_
)
disposable
=
attachDisposable
child
disposable
throwToResourceManager
(
RootResourceManager
child
exceptionHandler
)
ex
=
do
exceptionHandler
(
toException
ex
)
void
$
dispose
child
attachDisposable
(
RootResourceManager
child
_
_
_
)
disposable
=
attachDisposable
child
disposable
throwToResourceManager
(
RootResourceManager
_
stateVar
exceptionsVar
_
)
ex
=
do
-- TODO only log exceptions when disposing does not finish in time
traceIO
$
"Exception thrown to root resource manager: "
<>
displayException
ex
disposed
<-
liftIO
$
atomically
do
state
<-
readTVar
stateVar
-- Start disposing
when
(
state
==
RootResourceManagerNormal
)
$
writeTVar
stateVar
RootResourceManagerDisposing
let
disposed
=
state
==
RootResourceManagerDisposed
unless
disposed
$
modifyTVar
exceptionsVar
(
|>
toException
ex
)
pure
disposed
when
disposed
$
fail
"Could not throw to resource manager: RootResourceManager is already disposed"
instance
IsDisposable
RootResourceManager
where
dispose
(
RootResourceManager
child
_
)
=
dispose
child
isDisposed
(
RootResourceManager
child
_
)
=
isDisposed
child
dispose
(
RootResourceManager
_
stateVar
_
isDisposedAwaitable
)
=
do
liftIO
$
atomically
do
state
<-
readTVar
stateVar
-- Start disposing
when
(
state
==
RootResourceManagerNormal
)
$
writeTVar
stateVar
RootResourceManagerDisposing
pure
isDisposedAwaitable
isDisposed
(
RootResourceManager
_
_
_
isDisposedAwaitable
)
=
isDisposedAwaitable
newUnmanagedRootResourceManager
::
MonadIO
m
=>
m
ResourceManager
newUnmanagedRootResourceManager
=
liftIO
$
toResourceManager
<$>
do
stateVar
<-
newTVarIO
RootResourceManagerNormal
exceptionsVar
<-
newTVarIO
Empty
mfix
\
root
->
do
isDisposedAwaitable
<-
toAwaitable
<$>
unmanagedFork
(
disposeThread
root
)
child
<-
newUnmanagedDefaultResourceManager
(
toResourceManager
root
)
pure
$
RootResourceManager
child
stateVar
exceptionsVar
isDisposedAwaitable
where
disposeThread
::
RootResourceManager
->
IO
()
disposeThread
(
RootResourceManager
child
stateVar
exceptionsVar
_
)
=
do
atomically
do
state
<-
readTVar
stateVar
when
(
state
==
RootResourceManagerNormal
)
retry
-- TODO start thread: wait for timeout, then report exceptions or report hang
await
=<<
dispose
child
atomically
do
exceptions
<-
nonEmpty
.
toList
<$>
readTVar
exceptionsVar
mapM_
(
throwM
.
CombinedException
)
exceptions
withRootResourceManager
::
(
MonadAwait
m
,
MonadMask
m
,
MonadIO
m
)
=>
ReaderT
ResourceManager
IO
a
->
m
a
withRootResourceManager
action
=
bracket
newUnmanagedRootResourceManager
(
await
<=<
liftIO
.
dispose
)
(
await
<=<
dispose
)
(`
onResourceManager
`
action
)
newUnmanagedRootResourceManager
::
MonadIO
m
=>
m
ResourceManager
newUnmanagedRootResourceManager
=
liftIO
$
fixIO
\
self
->
do
var
<-
liftIO
newEmptyTMVarIO
childResourceManager
<-
newUnmanagedDefaultResourceManager
self
pure
$
toResourceManager
(
RootResourceManager
childResourceManager
loggingExceptionHandler
)
-- ** Default resource manager
data
DefaultResourceManager
=
DefaultResourceManager
{
parentResourceManager
::
ResourceManager
,
...
...
@@ -247,7 +294,6 @@ instance IsResourceManager DefaultResourceManager where
join
$
atomically
do
disposing
<-
readTVar
(
disposingVar
resourceManager
)
disposed
<-
readTVar
(
disposedVar
resourceManager
)
unless
disposing
$
modifyTVar
(
entriesVar
resourceManager
)
(
|>
entry
)
...
...
@@ -357,6 +403,7 @@ freeGarbage resourceManager = go
entriesVar'
=
entriesVar
resourceManager
-- * Utilities
-- | Creates an `Disposable` that is bound to a ResourceManager. It will automatically be disposed when the resource manager is disposed.
attachDisposeAction
::
MonadIO
m
=>
ResourceManager
->
IO
(
Awaitable
()
)
->
m
Disposable
...
...
This diff is collapsed.
Click to expand it.
test/Quasar/ResourceManagerSpec.hs
+
12
−
11
View file @
951fe87f
...
...
@@ -65,24 +65,25 @@ spec = parallel $ do
liftIO
$
throwIO
TestException
\
TestException
->
True
it
"
cancels the main thread when a dispose action fails
"
$
io
@
()
do
withRootResourceManager
do
with
Sub
ResourceManager
M
do
registerDisposeAction
$
throwIO
TestException
liftIO
$
threadDelay
100000
fail
"Did not stop main thread on failing dispose action"
it
"
passes an exception to the root resource manager
"
$
io
do
(`
shouldThrow
`
\
(
_
::
CombinedException
)
->
True
)
do
with
Root
ResourceManager
do
withSubResourceManagerM
do
registerDisposeAction
$
throwIO
TestException
liftIO
$
threadDelay
100000
it
"can attach an disposable that is disposed asynchronously"
$
io
do
withRootResourceManager
do
disposable
<-
captureDisposable_
$
registerDisposeAction
$
pure
()
<$
threadDelay
100000
liftIO
$
void
$
forkIO
$
await
=<<
dispose
disposable
it
"does not abort when encountering an exception"
$
do
it
"does not abort
disposing
when encountering an exception"
$
do
var1
<-
newTVarIO
False
var2
<-
newTVarIO
False
withRootResourceManager
do
registerDisposeAction
$
pure
()
<$
(
atomically
(
writeTVar
var1
True
))
registerDisposeAction
$
pure
()
<$
throwIO
TestException
registerDisposeAction
$
pure
()
<$
(
atomically
(
writeTVar
var2
True
))
(`
shouldThrow
`
\
(
_
::
CombinedException
)
->
True
)
do
withRootResourceManager
do
registerDisposeAction
$
pure
()
<$
(
atomically
(
writeTVar
var1
True
))
registerDisposeAction
$
pure
()
<$
throwIO
TestException
registerDisposeAction
$
pure
()
<$
(
atomically
(
writeTVar
var2
True
))
atomically
(
readTVar
var1
)
`
shouldReturn
`
True
atomically
(
readTVar
var2
)
`
shouldReturn
`
True
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment