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

Removing unliftio #1012

Merged
merged 6 commits into from
Nov 7, 2024
Merged

Conversation

kazu-yamamoto
Copy link
Contributor

This PR removes unliftio from WAI families.
This is because TLS bye and network gracefulClose cannot used with bracket of unliftio.

In my field testing for a week, I have not found any problems.
This PR is just to run CI.

@kazu-yamamoto kazu-yamamoto merged commit 288e38f into yesodweb:master Nov 7, 2024
14 checks passed
@kazu-yamamoto kazu-yamamoto deleted the removing-unliftio branch November 7, 2024 01:49
Copy link
Contributor

@Vlix Vlix left a comment

Choose a reason for hiding this comment

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

Overall, this change seems to me as a bit too invasive.

Changing to Control.Exception will change behaviour with regards to async exceptions. If we'd want to keep it similar without unliftio, we'd need to use safe-exceptions.
I'd only fall back to Control.Exception if we make sure the masking stays the same and async exceptions are handled the same.

Unless anyone has a good argument (in every spot where catch, throwIO, bracket, etc. is used) as to why we don't have to.

@@ -29,11 +29,11 @@ module System.TimeManager (
) where

import Control.Concurrent (myThreadId)
import qualified Control.Exception as E
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the main change because of this would be that AsyncExceptions will also be ignored if they are thrown during timeoutAction (whereas they wouldn't before)

So (I think?) this means a thread might be failed to be killed if the async exception is thrown while the timeoutAction is running?
So we might want to change to safe-exceptions instead of unliftio?

putMVar var mct
mbs <- takeMVar var
let tm = settingsTimeout set * 1000000
mbs <- timeout tm recvFirstBS
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this makes sense, but if we want to keep the same behaviour we'd have to change it to:

mbs <- unsafeUnmask $ timeout tm recvFirstBS

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original code before unsafeUnmask does not have unsafeUnmask.
Is it really necessary?

Copy link
Contributor

Choose a reason for hiding this comment

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

The umask in the diff is basically unsafeUnmask. But as you described earlier, that mkConn should be interruptable, this is unnecessary if the bracket (or whatever is used) is from Control.Exception instead of from UnliftIO.

Because the whole forkWithUnmask was a hack to get around the unliftio functions that mask more than necessary.

So no, I have changed my stance and adding unsafeUnmask is indeed not necessary.

@@ -366,7 +360,7 @@ httpOverTls TLSSettings{..} set s bs0 params =
case mconn of
Nothing -> throwIO IncompleteHeaders
Just conn -> return conn
wrappedRecvN recvN n = handleAny (const mempty) $ recvN n
wrappedRecvN recvN n = handle (\(SomeException _) -> mempty) $ recvN n
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, this will now also catch (and ignore) async exceptions.

@@ -2,10 +2,10 @@

module Network.Wai.Handler.Warp.Conduit where

import Control.Exception (assert, throwIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

This throwIO is fine if we're only throwing syncronous exceptions.

@@ -141,7 +141,7 @@ module Network.Wai.Handler.Warp (

import Data.Streaming.Network (HostPreference)
import qualified Data.Vault.Lazy as Vault
import UnliftIO.Exception (SomeException, throwIO)
import Control.Exception (SomeException, throwIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

This throwIO is fine if we're only throwing syncronous exceptions.

@@ -26,7 +26,7 @@ import System.Posix.IO (
openFd,
setFdOption,
)
import UnliftIO.Exception (bracket)
import Control.Exception (bracket)
Copy link
Contributor

Choose a reason for hiding this comment

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

The UnliftIO bracket is different in that it uses uninterruptibleMask_ on the after actions. How important are the terminate actions?
They are cleaning up file descriptors, so that might be important enough to absolutely make sure it happens, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resouce-release actions in bracket (i.e. masked actions) are iterruptible only if they are blocked (such as takeMVar).
Non-blocking actions are NOT iterruptible.
In other words, asynchronous exceptions are not delivered to resource-release actions if they are non-blocking.

terminate is non-blocking, so it's free from asynchronous exceptions, I believe.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, yes. Never mind. I've re-read the bracket docs, and it does. I misread, I think.

@@ -7,14 +7,14 @@ module Network.Wai.Handler.Warp.FileInfoCache (
getInfo, -- test purpose only
) where

import Control.Exception (bracket, onException, throwIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

UnliftIO.onException also runs with uninterruptibleMask. Making sure async exceptions wait until after the cleanup is done.

@@ -7,7 +7,7 @@ module Network.Wai.Handler.Warp.Types where
import qualified Data.ByteString as S
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.Typeable (Typeable)
import qualified UnliftIO
import qualified Control.Exception as E
Copy link
Contributor

Choose a reason for hiding this comment

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

import Control.Exception as E (Exception, SomeException)

@kazu-yamamoto kazu-yamamoto self-assigned this Nov 7, 2024
@kazu-yamamoto
Copy link
Contributor Author

@Vlix Thank you for reviewing.
I don't want to use safe-exception as alternative unliftio.
This is because each library has its own semantics which I don't understand completely.
One example is timeout cannot be used in bracket of unliftio which suffered me very much.
I would like to use only Control.Exception since it is well-documented and I know it well.
Even we would adopt safe-exception, we cannot let all other libraries to introduce it.
The mixed situation is anxious.
I would like to set up a proper handler for Control.Exception even though it is boring.
I believe it saves my time In the long run.
I will check your comments above tomorrow.

@Vlix
Copy link
Contributor

Vlix commented Nov 7, 2024

I would like to set up a proper handler for Control.Exception even though it is boring.

I can understand that.
I'd argue that we should try to mimic what unliftio did and change it in the situations where the unliftio style is not what we want.

@kazu-yamamoto
Copy link
Contributor Author

I remembered one this.
So, I write this up just for record.

Some people believe that unliftio makes our libraries less-buggy.
But the reality is that unliftio can introduce new bugs.
An example is that unliftio made makeConn of warp-tls uninterruptible.
The fix was 371279f.

@Vlix
Copy link
Contributor

Vlix commented Nov 8, 2024

Aaaah, that's why!

Ok, so what I think we should probably do:

  • identify the sections we want to be uninterruptible
  • identify which catches should let async exceptions through

Adding comments at those sections describing why we're using regular Control.Exception.catch or uninterruptibleMask or whatever would also be good for posterity.

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.

2 participants