Navigating the Source (Aeron Transport)
The domain modelling within Aeron Transport is very well done. If you read a description of something, whether it's the Driver Conductor, the Sender, a Network Publication or an Image, all of these concepts map directly to classes in the source code. Some classes are large, like the Driver Conductor, as it naturally has a lot of responsibilities. But it doesn't feel unwieldy once you become familiar with it.
Here are a few classes that serve as useful entry points for navigating through the source.
Client side
- Aeron - the
entry point to Aeron on the client side, for creating Publications and Subscriptions (called on an application thread)
addPubliction()
addExclusivePubliction()
addSubscription()
- ClientConductor
doWork()
- has a duty cycle, runs in its own thread by default
- ConcurrentPublication
offer() / tryClaim()
- client API for writing to a Publication log buffer from multiple threads
- ExclusivePublication
offer() / tryClaim()
- client API for writing to a Publication log buffer from one thread
- Subscription
poll()
- client API for reading message fragments
- Image
poll()
- reads from a Term, used bySubscription.poll()
Media Driver
- DriverConductor
doWork()
- has a duty cycle, runs in its own thread by default
- Sender
doWork()
- has a duty cycle, runs in its own thread by default
- Receiver
doWork()
- has a duty cycle, runs in its own thread by default
- NetworkPublication
send()
- called by the Sender threadupdatePublisherPositionAndLimit()
- update pub-pos and pub-lmt, called by the Driver Conductor thread
- IpcPublication
updatePublisherPositionAndLimit()
- update pub-pos and pub-lmt, called by the Driver Conductor thread
- PublicationImage
trackRebuild()
- detect loss, update rcv-pos and rcv-hwm, schedule Status Message
Navigation Example
Let's track a method call through the source code. Let's look at creating a Publication with Aeron.addPublication()
,
which is described in Creating a Publication. I won't link directly to
methods as they'll move over time.
Look through the source code yourself while following this.
Application thread
- Aeron.addPublication(channel, streamId)
- calls ClientConductor.addPublication(channel, streamId)
- calls DriverProxy.addPublication(channel, streamId)
- uses a PublicationMessageFlyweight
to write a PublicationMessage to the
toDriverCommandBuffer
- this is theto-driver
ring buffer in the cnc.dat file
- uses a PublicationMessageFlyweight
to write a PublicationMessage to the
- waits for a response
- calls DriverProxy.addPublication(channel, streamId)
- calls ClientConductor.addPublication(channel, streamId)
You can see the ClientConductor wants to call into the DriverConductor, but obviously can't because it's in a different process. Instead, it calls into a proxy to the Driver, the DriverProxy. This Proxy pattern is used in several places within Aeron, where a proxy class is used in place of the real target object. Seeing how to navigate through these will help elsewhere.
Now that a PublicationMessage
has been written into the to-driver
buffer, how do we find what reads that?
Go to the PublicationMessageFlyweight
class and look at usages of one of the getter methods, such as channel()
.
Ignoring tests, you'll usually find it's only called from one or two places. In this case, it's only called from
ClientCommandAdapter.addPublication()
.
But what calls that? It's called from ClientCommandAdapter.onMessage()
,
which is a callback defined in ControlledMessageHandler
that's called from a RingBuffer.
That makes sense as the PublicationMessage was written to a ring buffer. The ClientCommandAdapter is created in the
DriverConductor, which calls ClientCommandAdapter.receive()
in DriverConductor.doWork()
.
It's that receive()
method that's calling toDriverCommands.controlledRead()
to read from the to-driver
buffer.
So, we have this:
DriverConductor thread
Here's another pattern that's used widely - the Adapter pattern is used whenever a message is being read from a buffer and needs handling depending on its message type. Here, the DriverConductor is handling a message from the cnc.dat file.
addPublication()
branches depending on whether the channel is for an IPC or Network Publication. Let's say it's a
Network Publication and continue.
Here's another proxy. The SenderProxy (proxy to the Sender) varies depending on whether the Sender is running on the
same thread as the DriverConductor, which depends on the threading model.
If it's running on the same thread, the DriverConductor invokes Sender.onRegisterSendChannelEndpoint()
directly.
If not, it adds a Runnable to a command queue, where the Runnable invokes the method. This command queue is injected
into the SenderProxy and Sender. The Sender processes messages from the command queue in its doWork()
method.
I won't go into all the remaining details. Ultimately, the DriverConductor needs to send a message back to the application thread
to tell it that the Publication has been created. It calls into another proxy: ClientProxy.onPublicationReady()
,
which writes a PublicationBuffersReady message into the cnc.dat's to-clients
buffer. This is what the application
thread waiting for, polling in a loop.
Beware multiple threads
Some classes, like NetworkPublication are shared between the Driver Conductor and the Sender, so be aware of which
thread a method is being called from. All method calls will ultimately originate from the doWork()
method on one
of them, which will tell you which thread it's called from.