[comp.lang.modula3] 1.6 bugs

kalsow (Bill Kalsow) (04/06/91)

The following bugs have been reported in version 1.6:

1.  FileStream.OpenRead(x)  aborts with an assertion failure when
    x names a directory.  It should return a reader on the underlying
    file.

    Fix:  in system/corelib/filerw/UFileRdWr.m3,
      in CreateFileWriter line 356,
            change  <*ASSERT FALSE*> 
            to      RAISE Failure (openFailure);
      in CreateFileReader line 386,
            change  CoreOS.SIFREG =>
            to      CoreOS.SIFREG, CoreOS.SIFDIR =>
      in CreateFileReader line 389,
            change  <*ASSERT FALSE*>
            to      RAISE Failure (openFailure);      
     

2.  Multi-threaded programs may deadlock while terminating when there's
    a thread blocked in a select on stdin.  The problem is that RTMisc.Exit
    calls its registered cleanup routines.  Stdio registers a cleanup routine
    that calls Rd.Close.  It tries to lock the mutex in Stdio.stdin, but
    the reading thread already has the lock.  The runtime doesn't recognize
    this as a deadlock since the select could return at any time.

    This problem is especially obnoxious when the program is trying to abort.

3.  Formatter.Align was changed to take an object instead of a (procedure,
    REFANY) pair.  Formatter.m3 was incompletely updated.  This bug will
    cause assertion failures in programs that use Formatter (e.g. m3pp).

    Fix:  in libs/misc/formatter/Formatter.m3,
      in the main body, line 1069,
           change   DefineOp (PrintAlign,       5, 11)
           to       DefineOp (PrintAlign,       4, 11)

4.  In spite of their signatures, some routines in Rd don't raise Thread.Alerted.

    Fix:  in system/corelib/filerw/UFileRdWr.m3,
      in TerminalReaderSeek link 262,
           change  RTScheduler.IOSelect
           to      RTScheduler.IOAlertSelect

Thanks to Norman Ramsey and David Nichols for reporting these bugs.

  - Bill Kalsow

kalsow (Bill Kalsow) (04/08/91)

Sure enough, the scheduler's polling loop wasn't checking for alerts
in the threads that were blocked in I/O or timers.

Fix: system/corelib/thread/Thread.m3, in Thread.Yield, lines 332 - 387

   change:

      CASE t.state OF 
        | State.pausing  => 
            <<< case body 1 >>>
        | State.blocking =>
            <<< case body 2 >>>
        ELSE (* do nothing, these are not interesting *) END;

   to:

      CASE t.state OF 
        | State.pausing  => 
            IF (t.alertable) AND (t.alertPending) THEN
              CanRun (t);
            ELSE
              <<< case body 1 >>>
            END;
        | State.blocking =>
            IF (t.alertable) AND (t.alertPending) THEN
              CanRun (t);
            ELSE
              <<< case body 2 >>>
            END;
        ELSE (* do nothing, these are not interesting *) END;


  - Bill Kalsow

Peter.Robinson@computer-lab.cambridge.ac.uk (04/08/91)

| 4.  In spite of their signatures, some routines in Rd don't raise
|     Thread.Alerted.
| 
|     Fix:  in system/corelib/filerw/UFileRdWr.m3,
|       in TerminalReaderSeek link 262,
|            change  RTScheduler.IOSelect
|            to      RTScheduler.IOAlertSelect
| 
| Thanks to Norman Ramsey and David Nichols for reporting these bugs.

This fixes Norman Ramsey's problem, but a similar program that I have been
struggling with still doesn't seem to work.  The idea is to read a line of
input from stdin with a 10 second timeout if nothing is read:

  MODULE Alerts EXPORTS Main;
  
  IMPORT Rd, Stdio, Thread, Time, Wr;
  
  TYPE
    Getter = Thread.Closure OBJECT
               text:  TEXT;
               status     := "latent";
             METHODS
               apply := DoGet;
             END;
  
    Timer = Thread.Closure OBJECT
              interval: CARDINAL;
              victim:   Thread.T;
            METHODS
              apply := DoTime;
            END;
  
  PROCEDURE DoGet (self: Getter): REFANY RAISES {} =
    BEGIN
      TRY
        self.status := "reading";
        self.text := Rd.GetLine (Stdio.stdin);
        self.status := "finished";
      EXCEPT
        Thread.Alerted =>
          self.text := "";
          self.status := "interrupted";
      END;
      RETURN self;
    END DoGet;
  
  PROCEDURE DoTime (self: Timer): REFANY RAISES {} =
    BEGIN
      Time.LongPause (self.interval);
      Thread.Alert (self.victim);
      RETURN self;
    END DoTime;
  
  VAR
    getter := NEW (Getter);
    thread := Thread.Fork (getter);
  
  BEGIN
    EVAL Thread.Fork (NEW (Timer, interval := 10, victim := thread));
    TRY
      EVAL Thread.AlertJoin (thread);
      Wr.PutText (Stdio.stdout, "normal join\n");
    EXCEPT
      Thread.Alerted =>
        Wr.PutText (Stdio.stdout, "alerted join\n");
    END;
    Wr.PutText (Stdio.stdout, getter.status & "\n");
    Wr.Close (Stdio.stdout);
  
  END Alerts.

If I have understood it correctly, there should be three possible outcomes:

1.  You type quickly and get a normal join with a status of finished.

2.  You press return momentarily before the timer goes off and get an alerted
    join with a status of either reading or finished.

3.  You type too slowly and get a normal join with a status of interrupted.

The first works just fine.  The second occurs with probability approximately
zero and has not been tested experimentally.  The third is giving problems.

Until the latest release, it simply did not work;  Rd.GetLine never raised
Alerted.  With the fix to UFileRdWr.m3, the thread executing getter still
seems to be waiting on a select for terminal, even after being marked as
alerted.  The transcript goes as follows:

$ Alerts
test
normal join
interrupted
$ test: Command not found.
$

The line "test" is typed taking more than 10 seconds, but the alert is only
raised when the line is completed.  However, the input is then ignored and so
is picked up by the shell as the next command.

My guess is that the scheduler is proceeding as follows:

1.  getter is forked and the thread descheduled pending a select for the read.
2.  timer is forked and the thread descheduled pending a select for the
timeout.
3.  The scheduler blocks on a select for the read or the timeout.
4.  The timeout occurs and its thread is rescheduled;
    it marks getter as alerted and exits.
5.  The scheduler ignores the fact that getter is marked as alerted
    and blocks on another select for the read.
6.  When the line of typing is completed, the select returns, the scheduler
    notices the alert and duly raises Alerted, without the line being read.

Of course, I may have got hold of the wrong end of the stick.  Advice
welcomed.

- Peter Robinson.

nr@atomic.Princeton.EDU (Norman Ramsey) (04/08/91)

In article
<Mon.Apr..8.12:45:48.1991.@NewBrightonBelle:ComputerLab:CambridgeUniv>
Peter.Robinson@computer-lab.cambridge.ac.uk writes:  

> This fixes Norman Ramsey's problem, but a similar program that I have been
> struggling with still doesn't seem to work.  

You're not alone, Peter---the toy I posted now works, but my ``real''
program doesn't work yet---I have to hit a carriage return to
terminate a ``select'' call before my thread is alerted and my program
will terminate.  This problem isn't serious enough for me to take the
time to produce a toy that demonstrates the problem.

Norman
-- 
Norman Ramsey
nr@princeton.edu