Ticket #418 (new bug: None)

Opened 7 years ago

Last modified 3 years ago

throwTo to a thread inside 'block'

Reported by: remit Owned by:
Priority: lowest Milestone: _|_
Component: Runtime System Version: 6.4.1
Keywords: Cc:
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Incorrect result at runtime Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description (last modified by igloo) (diff)

[copy-pasting my original mail ( http://www.haskell.org/pipermail/glasgow-haskell-bugs/2005-June/005235.html)]

Good evening,

I just stumbled across a segfault caused when running the following small program. (During an attempt to implement single-assignment variables.)

> module Main where
> 
> import Control.Concurrent
> import System.IO.Unsafe (unsafeInterleaveIO)
> 
> main = do
>     v <- newEmptyMVar
>     a <- unsafeInterleaveIO (readMVar v)
>     t <- forkIO (print a)
>     threadDelay (1000*1000)
>     killThread t
>     forkIO (print a)
>     putMVar v ()

The crucial part about it seems to be the interruption of the lazy IO. Typing Ctl-c while running the first "print a" by hand from ghci instead of the forkIO+killThread doesn't change behaviour:

 Prelude System.IO.Unsafe Control.Concurrent> v <- newEmptyMVar
 Prelude System.IO.Unsafe Control.Concurrent> a <- unsafeInterleaveIO (readMVar v)
 Prelude System.IO.Unsafe Control.Concurrent> print a
 Interrupted.
 Prelude System.IO.Unsafe Control.Concurrent> forkIO (print a)
 Prelude System.IO.Unsafe Control.Concurrent> putMVar v ()
 zsh: segmentation fault (core dumped)  ghci

Both 6.4 and 6.2.1 crash when running main from ghci. When running it as a compiled executable everything is fine.

Although I'm pretty sure I've seen 6.2.1 crashing on it when run with -e main, I cannot reproduce it anymore. 6.4 certainly happily runs it with -e main. (A serious lack of sleep the last week may play a role too.. :-/)

Whether the module is compiled before being loaded into ghci has no effect.

Core-dumps etc can of course be sent if necessary.

Good night, Remi

Change History

Changed 7 years ago by remit

Logged In: YES 
user_id=26642

A few new datapoints. Using -threaded, 6.2.1 compiled
executables still survive, while 6.4 dies.

% ghc-6.4 -no-recomp foo.hs                 
% ./a.out                                   
% ghc-6.2.1 -no-recomp foo.hs 
% ./a.out                    

% ghc-6.4 -no-recomp -threaded foo.hs
% ./a.out                            
zsh: segmentation fault  ./a.out
% ghc-6.2.1 -no-recomp -threaded foo.hs
% ./a.out                              
% 

Also, the first forkIO can be changed into forkOS without
changing (crashing)behaviour, the second cannot.

Oh, and I won't be upset if this one won't be fixed for 6.4.1 ;)

Changed 7 years ago by simonmar

Logged In: YES 
user_id=48280

Interesting bug.  I've installed a workaround for the crash.
 It's tickled by having a thread blocked in takeMVar (or a
similar blocking operation), inside Exception.block (which
print does), inside unsafePerformIO (or unsafeInterleaveIO),
and sending the thread an exception.  When the
unsafePerformIO thunk is restarted again by another thread,
the Exception.block isn't re-instated properly.  This is a
bug, but highly unlikely to cause any real problems in
practice.  Besides I suspect we'll redesign this part of the
system in the light of STM at some point.

Changed 7 years ago by remit

Logged In: YES 
user_id=26642

When "this part of the system" is being redesigned, this
post may also become relevant again:
http://haskell.org/pipermail/glasgow-haskell-users/2005-June/008615.html

(in short:
STM can't cope with nested transactions, hence
main = atomically =<< unsafeInterleaveIO (atomically $
return $ return ()) 
coredumps)

Currently, it causes the trivial translation of this example
in terms of atomically + TMVar to coredump too.
The "raise an exception" solution seems _slightly_
unsatisfactory as it appears it should be possible to
implement single-assignment-vars with
MVar+unsafeInterleaveIO, without needing unsafePerformIO.

Changed 6 years ago by simonmar

  • difficulty set to Unknown
  • version changed from None to 6.4.1
  • os set to Unknown
  • architecture set to Unknown
  • description modified (diff)

Changed 5 years ago by igloo

  • owner changed from nobody to igloo
  • status changed from assigned to new
  • description modified (diff)

This seems to be fixed in 6.6. Leaving it open to remind me to add the example to the testsuite.

Changed 5 years ago by igloo

  • milestone set to 6.6.1

Actually, I'm not so sure this is working properly after all, now.

I'd expect this to manage to print () at some point even with the non-threaded RTS, but it doesn't:

module Main where

import Control.Concurrent
import System.IO.Unsafe (unsafeInterleaveIO)

main = do
    v <- newEmptyMVar
    a <- unsafeInterleaveIO (readMVar v)
    t <- forkIO (print a)
    threadDelay (1000*1000)
    killThread t
    forkIO $ do putStrLn "W1"
                print a
                putStrLn "W2"
    putStrLn "Q1"
    putMVar v ()
    putStrLn "Q2"
    putMVar v ()
    putStrLn "Q3"
    threadDelay (1000*1000)
    putStrLn "Q4"
    isEmptyMVar v >>= print
    putStrLn "Q5"
    putMVar v ()
    putStrLn "Q6"

I get:

Q1W1
Q2

Q3
Q4
False
Q5
conc067: thread blocked indefinitely

(the "thread blocked indefinitely" I do expect).

With the threaded RTS I get

Q1W1

()
W2
Q2
conc067: thread blocked indefinitely

which also seems wrong as I expect to be able to get all the way to Q5.

SimonM, what do you think?

Changed 5 years ago by simonmar

  • summary changed from unsafeInterleaveIO + Ctrl-C/killThread related segfault to throwTo to a thread inside 'block'
  • milestone changed from 6.6.1 to _|_

Following discussion with Igloo, both results are acceptable. It depends whether the putMVar inside readMVar gets to execute before the second putMVar in the main thread - there's a race, leading to two possible answers.

Leaving the bug open, as there's still a problem (see earlier comments). When we throw an exception to a thread inside 'block', and then restart the computation, the blocked state is not re-instated. This can only happen if the thread was executing block inside unsafePerformIO or unsafeInterleaveIO, so the bug is unlikely to bite many people.

Changed 4 years ago by igloo

  • owner igloo deleted

Changed 4 years ago by simonmar

  • architecture changed from Unknown to Unknown/Multiple

Changed 4 years ago by simonmar

  • os changed from Unknown to Unknown/Multiple

Changed 3 years ago by simonmar

  • failure set to None/Unknown

This bug is still present; I can't think of an easy way to fix it.

Changed 3 years ago by simonmar

  • failure changed from None/Unknown to Incorrect result at runtime
Note: See TracTickets for help on using tickets.