-
-
Notifications
You must be signed in to change notification settings - Fork 412
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
Add a field to Delayed for storing a clean up action #622
Conversation
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.
I think it's also pretty crucial to have tests for (a) the basic functionality, and (b) verifying that the cleanup action is run when exceptions are thrown in a range of situations.
@@ -398,7 +398,7 @@ instance HasServer Raw context where | |||
type ServerT Raw m = Application | |||
|
|||
route Proxy _ rawApplication = RawRouter $ \ env request respond -> do | |||
r <- runDelayed rawApplication env request | |||
(r, _) <- runDelayed rawApplication env request |
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.
Why isn't the cleanup run in Raw
?
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.
Oops, I just made it typecheck, not correct =) Should it be done by doing (case ...)
finallycleanup
to mimick the behavior we'll introduce in the runAction
function?
runAction action env req respond k = do | ||
(routeResult, cleanup) <- runDelayed action env req | ||
resp <- go routeResult | ||
cleanup |
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.
I think this should be bracket (runDelayed action env eq) snd (go . fst)
?
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.
It should indeed.
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.
Modulo a call to respond
.
@@ -103,6 +105,10 @@ data Delayed env c where | |||
, authD :: DelayedIO auth | |||
, bodyD :: DelayedIO body | |||
, serverD :: captures -> auth -> body -> Request -> RouteResult c | |||
, cleanupD :: body -> IO () |
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.
As I mentioned elsewhere, I don't know exactly why you need the body
arg. Or why just the body
, for that matter. DelayedIO (IO ())
seems like a more intuitive signature, to me.
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.
1/ Why is it more intuitive? Clean up will rarely (if ever) need to look at the raw request or perform routing.
2/ In my case, I get filepaths of temp files and what not by looking at body
. We can indeed add other arguments to cleanupD, but not make it a simple action, because I otherwise don't have any mean for my cleanup thing to get its hands on the temp files it is supposed to delete. More generally, this feature is useful anytime we want to clean up after body checks and other stuffs (anything, really). Except maybe for somewhat "global" stuffs, we need a way to for the clean up action to get its hands on whatever it is that needs cleaning up, so I use an argument for that.
I would not mind adding more arguments (results of the other checks). But making it a DelayedIO
so that I have to parse everything from the request again? No.
Yes, I'll write tests before it's approved for merging. |
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.
Generally I think this seems like a good thing to have.
I would also welcome a test that illustrates your exact use-case. So that we can more freely think about how to solve the problem in the simplest way.
, serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req | ||
{ bodyD = (,) <$> bodyD <*> new | ||
, serverD = \ c a (z, v) req -> ($ v) <$> serverD c a z req | ||
, cleanupD = cleanupD . fst -- not sure that's right |
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.
Seems right to me. :)
methodD | ||
a <- authD | ||
b <- bodyD | ||
liftIO (writeIORef cleanupRef $ cleanupD b) |
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.
I would feel better if we were able to use something like bracket
for this. Not sure that's easily doable.
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.
For writing the IORef
, or for the whole sequence of operations?
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.
Note that this is going to change a bit. @jkarni pointed out that adding clean up actions can happen from within bodyD
and that we should not wait for it to terminate in order to add a clean up action, because if an exception occurs the cleanup never gets run. Particularly annoying in the case of file uploads where files can pile up in /tmp.
@soenkehahn Yes, I'll add tests that mimic my use case as far as Delayed/cleanup are concerned. In the meantime, you might want to take a look here: https://github.com/haskell-servant/servant-multipart/blob/master/src/Servant/Multipart.hs#L267 |
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.
I'm +1 on this approach, but of course we need tests!
newCleanupRef = CleanupRef <$> newIORef (return ()) | ||
|
||
-- | Add a clean up action to a 'CleanupRef' | ||
addCleanup' :: IO () -> CleanupRef -> IO () |
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.
I'd just have this is a where
clause to addCleanup
so it's obvious that it's not meant to be used elsewhere.
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.
I agree
Route app -> app request (respond . Route) | ||
Fail a -> respond $ Fail a | ||
FailFatal e -> respond $ FailFatal e | ||
-- note: a Raw application doesn't register any cleanup |
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.
I think this is not just for consistency. E.g.:
type API = FileUpload :> Raw
Isn't in this case not having this going to cause the cleanup action to not be run? (It probably makes sense to have a test for (1) cleanup being run for Raw
; (2) cleanup only being run once)
…n use the result of bodyD to perform some IO clean up operation
… allowing early clean up registration
bd318c5
to
81a876c
Compare
Added some basic tests to make sure that clean up operations are run under various circumstances, among which the presence of exceptions in |
12a1dc1
to
6aab9be
Compare
, methodD = DelayedIO $ \_req_ _cl -> ok | ||
, authD = DelayedIO $ \_req _cl -> ok | ||
, bodyD = do | ||
liftIO (writeFile "delayed.test" "hia") |
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.
I'd prefer IORef
, so the tests don't write to disk.
@phadej @alpmestan is this superseded by more recent PRs? Can we close? |
AFAICS all comments here are addressed already. |
I need something like this in servant-multipart in order to delete files created in a temporary dir once the handler has run.
This is not the definitive code, but at least we can start a discussion.
For instance, @jkarni pointed out that the clean up might not always run with the code as is because of exceptions.
I decided to make the clean up actions live in
IO
(and notDelayedIO
) because most of the time, if not always, the code we want to run there lives inIO
and doesn't really need access to the request, but rather to something created in the body.