Hi;
I may have missed this in the docs, forgive me please if so…
If I have two or more processes (tasks perhaps?) that are reading and/or writing to a file, how can I obtain temporary locking?
I certainly do not want file corruption.
I really do not want to have an unhandled exception from being uable to get access to the file by the second process while the first process does have access, which would result in the program exiting.
Perhaps I have to do exception handling, have a delay, and try again, if the second process can’t get access?
Perhaps “good” Ada programmers in a program that does multiprocessing only has one task that does the I/O for a specific file and it performs that locking and the rendezvous synchronization so that all the other processes have to busy wait for the task responsible for I/O to accept requests for the file?
Thanks,
Retired Build Engineer
Re-reading the docs…I should just use the “Is_Open” function provided by Ada.Text_IO, and then use a busy-wait loop if necessary until available?
I guess I was over-thinking it
Thanks,
Retired Build Engineer
1 Like
One method would be to have a task-type, something like:
-- Assuming with/use of Ada.Text_IO & Ada.Strings.Unbounded.
Task Type Write_Control( File : not null access File_Type ) is
Entry Write( Input : String );
Entry Done;
End Write_Control;
-- and, maybe:
Function Create( File : in out Ada.Text_IO.File_Type ) return Write_Control is
( Write_Control( File => New Ada.Text_IO.File_Type'(File) ) );
-- …
Task Body Write_Control is
Package US renames Ada.Strings.Unbounded;
Working : US.Unbounded_String;
Finished: Boolean:= False;
Function "+"(Right : US.Unbounded_String) return String
renames US.To_String;
Function "+"(Right : String) return US.Unbounded_String
renames US.To_Unbounded_String;
Begin
Loop
Exit when Finished;
select
Entry Write( Input : String ) do
Working:= +Input;
End Write;
Ada.Text_IO.Put( Item => +Working, File => File.All );
or
Entry Done;
Finished:= True;
end select;
End loop;
End Write_Control;
This forces access to the file through the task’s entry
, which can only be accessed by a single task at a time, giving you the appropriate control… I typically use a helper-function that takes the file’s name, opens the file (creating it if it doesn’t exist) and returning that – an excellent case for using extended-return:
Function Create(Name : String) return File_Type is
Begin
Return Result : File_Type do
-- Attempt open, on exception
-- attempt to create.
End return;
End;
This construction means that I’ll get an exception, or the function-call returns the file, opened and ready to use…
(Note this is on-the-fly/off-the-cuff non-compiled code.)
1 Like
The “monitor” solution was already presented for single process. Here is a solution based on a mutex for the sake of completeness:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Finalization;
with Ada.Task_Identification;
with Synchronization.Mutexes;
procedure Main is
package Shared_Files is
type Shared_File is tagged limited private;
procedure Create (F : in out Shared_File; Name : String);
procedure Put_Line (F : in out Shared_File; S : String);
private
use Synchronization.Mutexes;
type Shared_File is
new Ada.Finalization.Limited_Controlled with
record
File : File_Type;
Lock : aliased Mutex;
end record;
procedure Finalize (F : in out Shared_File);
end Shared_Files;
package body Shared_Files is
procedure Finalize (F : in out Shared_File) is
Exclusive : Holder (F.Lock'Access);
begin
if Is_Open (F.File) then
Close (F.File);
end if;
end Finalize;
procedure Create (F : in out Shared_File; Name : String) is
Exclusive : Holder (F.Lock'Access);
begin
Create (F.File, Out_File, Name);
end Create;
procedure Put_Line (F : in out Shared_File; S : String) is
Exclusive : Holder (F.Lock'Access);
begin
Put_Line (F.File, S);
end Put_Line;
end Shared_Files;
use Shared_Files;
File : Shared_File;
task type Spammer;
task body Spammer is
use Ada.Task_Identification;
begin
for I in 1..100 loop
File.Put_Line (Image (Current_Task) & ":" & Integer'Image (I));
end loop;
File.Put_Line (Image (Current_Task) & ": Bye");
end Spammer;
begin
File.Create ("test.txt");
declare
Talkers : array (1..10) of Spammer;
begin
null;
end;
end main;
It starts 10 tasks writing a shared file concurrently.
Now, if we talk about files shared between processes. Here are the points:
- Files must be opened for shared access.
- You can use a system-wide mutex with the solution above or else an RPC for “monitor” solution.
In Simple components for Ada you can find both interprocess mutex and RPCs. But before you even start, consider it a bad idea. A system logging facility or a database would be most likely a far better idea.
1 Like
I rather like being able to abstract out the DB/logger into its own task, if possible.
If you do it right, you can essentially use “MVC” on the front-end and the back-end, keeping your program’s objects/data homogeneous and abstracting away any notion that (e.g.) the back-end is CRUD database actions.
It really is sad how many programmers are unaware of the technique because so many applications leak those details because (a) C/PHP is bad at abstracting it out; (b) a library has a strong notion of how data/objects are handled/processed, and it’s “easier” to just work on those terms; and/or (c) management bought the notion that “application development = writing glue-code between various libraries”.
1 Like