Skip to content
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

Closed
wants to merge 6 commits into from

Conversation

alpmestan
Copy link
Contributor

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 not DelayedIO) because most of the time, if not always, the code we want to run there lives in IO and doesn't really need access to the request, but rather to something created in the body.

Copy link
Member

@jkarni jkarni left a 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
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should indeed.

Copy link
Contributor Author

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 ()
Copy link
Member

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.

Copy link
Contributor Author

@alpmestan alpmestan Oct 21, 2016

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.

@alpmestan
Copy link
Contributor Author

Yes, I'll write tests before it's approved for merging.

Copy link
Contributor

@soenkehahn soenkehahn left a 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
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

@alpmestan
Copy link
Contributor Author

@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

Copy link
Member

@jkarni jkarni left a 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 ()
Copy link
Member

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.

Copy link
Contributor

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
Copy link
Member

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)

@alpmestan alpmestan mentioned this pull request Jan 7, 2017
@alpmestan
Copy link
Contributor Author

Added some basic tests to make sure that clean up operations are run under various circumstances, among which the presence of exceptions in bodyD or serverD. Also rebased against master. Could anyone take a look and let me know what's missing?

@phadej phadej modified the milestone: 0.10 Jan 13, 2017
, methodD = DelayedIO $ \_req_ _cl -> ok
, authD = DelayedIO $ \_req _cl -> ok
, bodyD = do
liftIO (writeFile "delayed.test" "hia")
Copy link
Contributor

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.

@jkarni
Copy link
Member

jkarni commented Jan 18, 2017

@phadej @alpmestan is this superseded by more recent PRs? Can we close?

@phadej
Copy link
Contributor

phadej commented Jan 18, 2017

AFAICS all comments here are addressed already.

@phadej phadej closed this Jan 18, 2017
@jkarni jkarni deleted the delayed-cleanup branch February 1, 2018 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants