depressed-pho/HsOpenSSL

Support typechecking initialization

singpolyma opened this issue · 8 comments

I can understand why this library might not want to be super-opinionated about the right way to statically check that withOpenSSL is being used correctly. I would like to propose that some very lightweight machinery be added to make it possible for downstream authors to add such if they want:

module OpenSSL (InitOpenSSL, withOpenSSL) where
    data InitOpenSSL = InitOpenSSL -- Constructor not exported

    withOpenSSL :: (InitOpenSSL -> IO a) -> IO a
    withOpenSSL act = do
        loadErrorStrings >> addAllAlgorithms >> libraryInit >> setupMutex
        act InitOpenSSL

The existing common use can still be easily supported:

main = withOpenSSL $ const $ do ...

And if library authors wish, they can now require an argument of type InitOpenSSL, or use a ReaderT, or use ImplicitParams, or whatever method they like to statically check that the init has been done, and authors who do not wish to do so still need not wade through much machinery from this library to use it for their purposes.

Why can't we just put a global guard in? This is a native library we're talking about; C side I'd just whack a boolean in a static somewhere and be done with it.

@afcowie So that library authors can just redundantly call it for you? I guess that would work. Though the global hack always feels brittle to me.

@afcowie Hmm... Something like this?

module OpenSSL (withOpenSSL, isInitialized) where
    isInitialized :: IO Bool
    withOpenSSL :: IO a -> IO a

Dynamic checks are surely better than no checks at all. But I'm relatively sympathetic to @singpolyma's idea.
Still thinking about it...

So in C we would just do:

static int initialized = 0;

void open_library() {
    if (!initialized) {
        handle = do_stuff_only_once();
        initialized = 1;
    }
}

I'm not really sure that's brittle, if only because I've used it and seen it used in so much code its a defacto idiom in native code. I imagine what you meant @singpolyma was "the global variable hack in Haskell seems brittle" which it may be, only because we're used to stronger type safety.

But yes, I'd be going for "so library authors can just call it for you". I don't think there's anything particularly wrong with what you wrote. If @phonohawk wants to do isInitialized, then that works for me too — I'll just call it on every single request as a guard, and would be better if you want to leave the existing semantics of withOpenSSL-only-once.

But it sure would be nice to be able to do

get :: URL -> IO a
get url = withOpenSSL $ internalImplementationOfGet url

(sic) and just have it be transparently multi-invocation safe.

AfC

@afcowie Oh I got it. I've never thought of applying withOpenSSL many times, which should be legitimate indeed. I'll reimplement it soon.

On that note, since withOpenSSL doesn't really scope anything or clean anything up at all, why not initOpenSSL :: IO () ?

[I'm guessing the pattern was inherited from Windows machines needing to do withSocketsDo which no one else needs to bother with]

Released 0.10.3.5:
http://hackage.haskell.org/package/HsOpenSSL-0.10.3.5

@singpolyma Right, but someday we may end up wishing to scope something.
@afcowie You guessed right.