Task Safe Use of Streams

A stream is just as safe in a concurrent design as any other shared
resource.  That is, unless it is properly protected, it is not at all safe.
The same may be said of shared memory, or shared files.

There are two classical solutions to this problem.  The first is to
elminate the shared resource, and have a single, unshared resource
for each task.  The second is to protect the shared resource by having
only one task at a time access the resource.

Classical database engines control concurrent access to the
database by forcing all tasks to access the database through a
single database engine.  The same sort of thing can be achieved
in Ada in a couple of ways.  One way is to have all tasks send
messages to a single controlling task through a rendezvous.  This
would be the typical Ada 83 approach.  The more modern, and
more efficient approach in Ada 95 is to have encapsulate the stream
in a protected object.  All access will be correctly and effectively
controlled by the protected object mechanism.

When you use a protected object the "read" and "write" procedures
or entries must have a parameter of a class-wide type.  The
class-wide type parameter will allow the protected object to
read and write a wide range of otherwise heterogeneous data items
to and from the stream.  The stream may simply be an instance of
Ada.Streams.Stream_Io, or it may be a custom-made stream of
your own choosing.  The implementation will be irrelevant to the
tasks accessing the protected object.  It will also be irrelevant to the
protected object itself.

Following is a modest example of what I mean about
encapsulating a stream in a protected object.

The example uses Ada.Streams.Stream_Io for simplicity.  You can
reasonably substitute your own stream implementation as needed.

Note that the file name for the log file is passed as a discriminant to
the protected type.

I believe this solution solves the interleaving problem.  The read and
write operations will be atomic.  The write operation will not complete
until all 'write operations for the tagged type complete.  The key is
that the task calling the protected write and read operations is
ignorant of the use of a stream.  No stream operations occur outside
the protection of the protected type.

-- Simple Example of using streams in a protected object for access by
-- multiple tasks.
   package Type_Hierarchy is
      type Base_Type is tagged private;

   -- Add dispatching operations to the Base_type

      type First_Child is new Base_Type with private;

   -- Add or override operations for First_Child


      type Base_Type is tagged record
            Date : String(1..10);
            Time : String(1..8);
         end record;

      type First_Child is new Base_type with record
            Voltage : Float;
         end record;
   end Type_Hierarchy;

   with Type_Hierarchy;
   use Type_Hierarchy;
   with Ada.Steams.Stream_Io;

   package Multi_Task_Logging is

      protected type Log_Stream (Log_Name : String) is
         Procedure Write(Item : in Base_Type'Class);
         Entry Read(Item : out Base_Type'Class);
         File : Ada.Streams.Stream_Io.File_Type;
         Log_Access : Ada.Streams.Stream_Io.Stream_Access :=
            Ada.Streams.Stream_Io.Stream(File => File);
      end Log_Stream;
   end Multi_Tasking_Logging;

   package body Multi_Tasking_Logging is

      protected body Log_Stream is
         Procedure Write(Item : in Base_Type'Class) is
            if not Ada.Streams.Stream_Io.Is_Open(File) then
               Ada.Streams.Stream_Io.Open(File => File,
                                          Name => Log_Name,
                                          Mode =>
               Ada.Streams.Stream_Io.Reset(File => File,
                                           Mode =>
            end if;
            Base_Type'Class'Write(Stream => Log_Access, Item => Item);
         end Write;

         Entry Read(Item : out Base_Type'Class) when not
         Ada.Streams.Stream_Io.End_of_File(File) is
            if not Ada.Streams.Stream_Io.is_Open(File) then
               Ada.Streams.Stream_Io.Open(File => File,
                                          Name => Log_Name,
                                          Mode =>
               Ada.Streams.Stream_Io.Reset(File => File,
                                           Mode =>
            end if;
            Base_Type'Class'Read( Stream => Log_Access, Item => Item);
         end Read;
      end Log_Stream;

   End Multi_Tasking_Logging;

Contributed by: James S. Rogers Contributed on: December 30, 1998
License: Public Domain