[MLton] Bug in CML mailbox.sml

Ray Racine rracine@adelphia.net
Sun, 13 Mar 2005 12:00:46 -0500


The small program at the bottom fails as follows:

[ray@gan common]$ mlton mbox.mlb
[ray@gan common]$ ./mbox
Msg::0
Msg::1
Msg::2
Msg::3
Msg::4
unhandled exception: Fail: Thread.atomicEnd with no atomicBegin


What happens is if the mbox is used as in the context of a revcEvt and
the mailbox queue does not have a credit of msgs, but has a debit of at
least one waiting thread the above happens.

Either the queued receiving thread is in the wrong atomic context or the
switch to the queued thread assumes the wrong context.   

The one line fix below assumes the latter.  Not sure if this is THE fix
however.

In CML's mailbox.sml source file, send () function, on or about line 63.

case !state of 
  EMPTY q => (case (cleanAndDeque q) of
    (NONE, _) => 
	(let val q = Q.new ()
	in state := NONEMPTY (1, Q.enque (q, x))
	end
	; debug' "send(3a)"
	; S.atomicEnd())
	| (SOME (transId', t'), q') =>
	    (  debug' "send(3b)";
(** FIX is change to S.readyAndSwitch **)
        S.atomicReadyAndSwitch
	   (fn () =>
		(state := EMPTY q'
		; TransID.force transId'
		; S.prepVal (t', x)))))       			    
		| NONEMPTY (p, q) => 
.....

------ BUG Creating Program -----

structure Main =
struct

  datatype tmsg = Msg of int
		| Timeout
		  
  val mbox = Mailbox.mailbox ()

  fun write s = 
      (
       TextIO.output ( TextIO.stdOut, s );
       TextIO.flushOut TextIO.stdOut
      )
            
  structure Consumer = 
  struct
    
    fun recv () = 
	let fun handleMsg m = 
		MLton.Thread.atomically
		    ( fn () =>					     
			 case m of
			     Msg n => write ( concat [ "Msg::", 
						       Int.toString n, 
						       "\n" ] )
			   | Timeout => write "Timeout rec\n" )
	in
	    CML.select [ CML.wrap ( Mailbox.recvEvt 
					mbox, 
				    handleMsg ),
			 CML.wrap ( CML.timeOutEvt 
					( Time.fromSeconds 3 ), 
				    ( fn () =>  
					 handleMsg Timeout ) ) ];
	    recv ()
	end
	
    fun run () = CML.spawn recv
		 
  end

  structure Producer = 
  struct

    fun send n = let val n' = n + 1
		 in
		    Mailbox.send ( mbox, Msg n );
		    CML.sync ( CML.timeOutEvt 
				   ( Time.fromSeconds 1 ) );
		    send n'
		 end

    fun run () = CML.spawn ( fn () => send 0 )

  end

  fun run () =
      (
       Producer.run ();
       CML.sync ( CML.timeOutEvt 
		      ( Time.fromSeconds 5 ) );
       Consumer.run ()
      )

  fun main () =
      RunCML.doit ( ignore o run , NONE )

end

val _ = Main.main ()