Okay I've spent some time thinking about some of this stuff. I've come up with an idea that I think will keep the flexibility that I want and keep the module interface simple. There are basically two types of message transactions. Some messages are just put on the queue an then forgotten. It is assumed that the receiver will eventually get the message if it is still alive. Some messages require a response. Such as a read tag message. We have to ask for the tag and then wait for the response. This is where the problem starts. While the module is building the message to send the request to read the tag the OpenDAX core puts an event message on the queue. Then the module sends the request and immediately calls the message receiving function (basically a wrapper for msgrcv()). The msgrcv() function will receive the event message an not the response to the tag read.
This situation is not too bad, because I simply need to send the event message to the function that handles the events and then go right back into msgrcv() to wait for the response to my tag read request. This is why I/O modules would do just fine. They'll find themselves in the _message_recv() function quite often so the events will be captured in a timely manner. However, what if we are waiting on other events. Waiting for a mouse click and blocking in the msgrcv() function are not going to work well. Adding threads to handle this simply makes the problem worse because the different threads are going to receive each others messages.
I considered an internal message queue for the process. If the msgrcv() function gets a message that it doesn't know how to handle it simply puts it on this internal queue and then part of the _message_recv() wrapper function is to check the queue before it falls into the msgrcv() function and blocks. The more I thought about this the more I realized that it doesn't really solve the problem because a second thread could be putting the message on our internal queue between the time that we check the internal queue and then go to block in msgrcv(). This could be solved with some concurrency checking devices but this makes module programming far too complex and that is what I am primarily trying to avoid.
I need to separate the two different messages. I think what I will do is add a bit to the message type field. Right now I am using the modules PID as the message type. This way the msgrcv() function will only look for messages that are destined for this module. The message type field in the message structure is a long int and the PID type is an int. I have a mess of bits to differentiate the different types of messages and the lower bits will be unique for each module. Now I add a bit to differentiate a response message.
So when the module sends a message that would require a response it will add a bit to the message type and wait for that message instead of one that is simply the PID of the process. It'd be the PID + some number. I don't know how portable this will be but I think that Linux and BSD are safe. Now when an asynchronous message comes in from an event (or another module), it won't be picked up by the function that is waiting for a response be cause the message type will be different.
Now I need to create two more functions. One that simply checks to see if there is a message on the queue (lets call it dax_poll()) if there is a message it'll call the proper message handling function. If not it'll return 0. The other function (lets call it dax_wait()) would do the same thing but it would block waiting for a message.
Okay, assuming that I have the response messages and the event messages separated I think I need a way to have the core send signals to the module when an event occurs. I think this should be configurable for each module. If the module is going to be a polling type module it won't need the signals since it would find itself in the dax_poll() or dax_wait() functions periodically it would receive it's messages in a timely manner. Otherwise, the module could request the core to send it a signal (lets use SIGUSR1 just for grins). This would solve the problem of handling events when waiting for user input. The daxc module spends most of it's time in the readline() function waiting for a user to enter a command. This command should exit when a signal is sent. So if the readline() function exits due to a signal the module simply calls dax_poll() to see what the event is and then go back into readline().
The signals should be sent after all the event messages are on the queue. A module may actually get more than one event every time the core runs and we don't want to be sending too many signals in a row. It would be up to the module to call dax_poll() repeatedly until there are no more events to handle.
Now if a module wants to implement an event driven architecture it can simply block in dax_wait() until it gets an event and respond to it. Or it can simply call dax_poll() occasionally to receive its messages. Of course the simplest of all modules wouldn't receive any events at all. I still have to have a way for the system to deal with stray messages in the queue but that is discussed elsewhere and needs to be done regardless.
If the module is going to be multi-threaded and each thread capable of sending request/response messages then the module programmer will have to deal with those mutexs himself, but this is acceptable since multi-threaded programming requires that knowledge in the first place.
Now I just need to figure out how to handle the event interface in the library. But that's another days problem.