A few years ago,
[Ada](https://secure.wikimedia.org/wikipedia/en/wiki/Ada_(programming_language)
became my hobby/tinker programming language of choice, for a number of
reasons, concurrency being
one of them. In this post I’d like to walk you through an example of dynamic
task creation in Ada, which uses Ada.Task_Termination
handlers, a new feature
in Ada 2005.
(If you’re familiar with Ada, you can skip this next section)
Note: You can find all this code, and more in my ada-playground repository on GitHub
Similar to C, Ada supports stack allocated variables as well as heap allocated variabels, it also defaults to stack allocation. For example:
If you wanted to allocate that Integer
onto the heap, then you would use the
new
keyword:
I won’t dive too much into the minutia of what is going on here, if you’re not
familiar with Ada already you can learn more about access types on the Ada
Programming
Wikibook. Basically
we’re heap allocating a new Integer and using an access type (aka: typed
pointer) to keep track of it. Keen readers will notice we didn’t do anything
with that Integer access type, and we’re technically leaking the memory. To
solve this we use the generic unit Ada.Unchecked_Deallocation
, which gives
you a facility for properly freeing memory (more details
here).
Tasking Trickiness
Concurrency is part of the language in Ada, and is handled through tasking. A basic example of might be:
The way tasks in Ada work means that the Counter
task will be created,
started and then the execution of the Main
program will block until the
Counter
task completes (important detail).
The trickiness starts to arrive when you talk about dynamically allocating task objects, and combine that with something like an infinite loop, such as one might find in a server program, e.g.:
(The code above is an abbreviated version of echomultitask_main.adb
which can
be found
here).
The issue with this code is that we’re allocating a new Handler
task for
every in-bound connection, and we have no means of ever cleaning them up
properly. If we were to create an Array of Handler_Ptr
, we still would have
to find some mechanism (which exists) to check the status of each Handler
to
determine if we should clean it up. Problem being, we’d have to loop through
all the active tasks, checking for a “terminated” status, in order to
deallocate them. It’d be much better if a task could tell us when it’s
finished, rather than us polling every one.
Fortunately in Ada 2005, a mechanism was added to make it easier to add
“clean-up” to tasks: Ada.Task_Termination
. The package allows you to set
up a termination handler for the a specific task, which the runtime will call
when that task terminates. Unfortunately however, the handler procedure that
can be invoked when the task terminates will not be passed a pointer to
the task itself, but rather the Task_Id
(Ada.Task_Identification.Task_Id
).
So close to being able to properly deallocate these dynamic tasks, but we need one more component, a protected object with a hash map inside of it:
Then back in main.adb
:
(The code above is an abbreviated version of echomultitask-worker.ads
which
can be found
here)
The singleton protected object Coordinator
not only will give us protected
(aka thread safe) access to the Active_Tasks
map, but also gives us a
protected object to hang our Ada.Task_Termination.Termination_Handler
protected procedure off of:
(The code above is an abbreviated version of echomultitask-worker.adb
which
can be found here)
This approach will allow us to safely create new dynamic tasks to handle the incoming requests, but will also make sure that the tasks are cleanly deallocated when they terminate.
If you’re interested in concurrency in Ada, I highly recommend purchasing Concurrent and Real-Time Programming in Ada by Alan Burns and Andy Wellings, it’s been tremendously helpful for my own concurrency exploration in Ada.