
John's approach is the right one, but note that the "STM setup work" will
be visible to other transactions. Any work done there must be considered
consistent as it will be committed. For instance if we have the following:
atomically $ do
m <- readTVar mutex
check (not m)
writeTVar m True
writeTVar a True
ffiCall
atomically $ do
writeTVar b True
writeTVar m False
If it should be the case that `a` is true only if `b` is true then we could
run into problems with some other transaction that is not concerned with
the `ffiCall` but is concerned about `a` and `b`.
Another approach that might be viable is to just fix your foreign call's
interface by putting a lock in the foreign code. If it really is a pure
call, then there is no danger of deadlocking there. This will then be as
safe as any foreign call inside STM. There are dangers here of course.
One particular danger is if your call takes multiple arguments that need
to be consistent, you can see inconsistency inside a failed transaction
before the runtime system has determined that it is a failed transaction.
For instance:
atomically $ do
x <- readTVar a
y <- readTVar b
return $ unsafePerformIO $ ffiCall x y
Say `x` is an array and `y` is an index into that array. Even if your
transactions keep these consistent, you could send inconsistent data to the
foreign call. You can fix this by checking (at the value level)
the integrity of the data or changing the granularity of the data and
putting values `x` and `y` into the same `TVar`.
Beyond this problem I don't know what other issues foreign calls inside STM
face, but I would love to hear what others know.
Ryan
On Mon, Feb 3, 2014 at 6:17 AM, John Wiegley
Rob Leslie
writes: Is this expected behavior? I realize performing any IO within an STM transaction is inherently unsafe, but I am a little surprised that 'bracket' fails here.
Is there a better way to do what I'm trying to accomplish? How can I provide a pure interface to my foreign function that will work within an STM transaction?
If your foreign function "depends on some global state in a way that compromises thread safety", I would hestitate to simply tell the FFI that it is pure.
Instead, you can break up your STM transaction into two pieces: A first part that sets up the transaction and sets a guard variable so other transactions cannot proceed until the second part is completed, then perform the FFI call, then the second part of the transaction. For example:
atomically $ do m <- readTVar mutex check (not m) writeTVar m True ... do whatever STM setup work is needed here ... ffiCall atomically $ do ... do whatever STM cleanup work is needed here ... writeTVar m False
This way your ffiCall is conceptually within a larger transactional block.
John _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe