views:

425

answers:

6

I'm using Qt framework which has by default non-blocking I/O to develop an application navigating through several web pages (online stores) and carrying out different actions on these pages. I'm "mapping" specific web page to a state machine which I use to navigate through this page.
This state machine has these transitions;
Connect, LogIn, Query, LogOut, Disconnect
and these states;
Start, Connecting, Connected, LoggingIn, LoggedIn, Querying, QueryDone, LoggingOut, LoggedOut, Disconnecting, Disconnected
Transitions from *ing to *ed states (Connecting->Connected), are due to LoadFinished asynchronous network events received from network object when currently requested url is loaded. Transitions from *ed to *ing states (Connected->LoggingIn) are due to events send by me.
I want to be able to send several events (commands) to this machine (like Connect, LogIn, Query("productA"), Query("productB"), LogOut, LogIn, Query("productC"), LogOut, Disconnect) at once and have it process them. I don't want to block waiting for the machine to finish processing all events I sent to it. The problem is they have to be interleaved with the above mentioned network events informing machine about the url being downloaded. Without interleaving machine can't advance its state (and process my events) because advancing from *ing to *ed occurs only after receiving network type of event.

How can I achieve my design goal?

EDIT

  1. The state machine I'm using has its own event loop and events are not queued in it so could be missed by machine if they come when the machine is busy.
  2. Network I/O events are not posted directly to neither the state machine nor the event queue I'm using. They are posted to my code (handler) and I have to handle them. I can forward them as I wish but please have in mind remark no. 1.
  3. Take a look at my answer to this question where I described my current design in details. The question is if and how can I improve this design by making it

    • More robust
    • Simpler
+1  A: 

Strictly speaking, you can't. Because you only have state "Connecting", you don't know whether you need top login afterwards. You'd have to introduce a state "ConnectingWithIntentToLogin" to represent the result of a "Connect, then Login" event from the Start state.

Naturally there will be a lot of overlap between the "Connecting" and the "ConnectingWithIntentToLogin" states. This is most easily achieved by a state machine architecture that supports state hierarchies.

--- edit ---

Reading your later reactions, it's now clear what your actual problem is.

You do need extra state, obviously, whether that's ingrained in the FSM or outside it in a separate queue. Let's follow the model you prefer, with extra events in a queue. The rick here is that you're wondering how to "interleave" those queued events vis-a-vis the realtime events. You don't - events from the queue are actively extracted when entering specific states. In your case, those would be the "*ed" states like "Connected". Only when the queue is empty would you stay in the "Connected" state.

MSalters
That's nice information but it's not an answer to my question. Right now I always log in after connecting so that's why I have only `Connecting` state. Let's suppose I'll add `ConnectingWithIntentToLogin` state. Now what? The question's merit is how to force interleaving of events.
Piotr Dobrogost
Simple: if in state ConnectingWithIntentToLogin you receive a "Connect" event, you do not transition to "Connected". Instead you execute a Login command and move to state "Logging in". What's there to "interleave"?
MSalters
@MSalters I only have one kind of network event which is `LoadFinished`. How can you know being **inside** handler for this event what kind of event should you generate? Currently when I receive this network event I always send the same `Loaded` event to the machine and it makes transition to let's say `Connected` state when already in `Connecting` state or transition to let's say `LoggedIn` state if already in `LoggingIn` state.
Piotr Dobrogost
Oh, that detail I missed. But it's pretty irrelevant then. It just means that LoadFinished must be handled by all states in which it could be received. Those states would *not* be states in which you'd pull an event from the queue.
MSalters
@MSalters From your comments I see you understand the subject well so I would like to hear more comments from you. The situation you describe is how it's now. I have a strong feeling that in current design, logic of my app is mixed up with logic of web page navigating, however. These are two different things and I would like to find design that would separate them. I have also tough problem deciding where the data should be put and transmitted - in events, in transitions, in slots or maybe in more than one place? This is interesting problem and maybe I should ask this in a new question. Ideas?
Piotr Dobrogost
@MSalters You proposed adding new states to my state machine. Simultaneously I've got just an opposite advice here http://www.qtcentre.org/forum/p-how-to-design-a-state-machine-in-face-of-non-blocking-io-post113027/postcount4.html Could you please take a look at tell me what you think of it?
Piotr Dobrogost
+4  A: 

Sounds like you want the state machine to have an event queue. Queue up the events, start processing the first one, and when that completes pull the next event off the queue and start on that. So instead of the state machine being driven by the client code directly, it's driven by the queue.

This means that any logic which involves using the result of one transition in the next one has to be in the machine. For example, if the "login complete" page tells you where to go next. If that's not possible, then the event could perhaps include a callback which the machine can call, to return whatever it needs to know.

Steve Jessop
Nice proposal but it doesn't solve the problem. If I enqueue my events machine will be stuck waiting for network event...
Piotr Dobrogost
Oh, unless you mean that the network events would be stuck in the queue behind the events the client code is issuing. In which case you need *another* queue - one which the network object is using for asynchronous I/O completion, and another which is just there to remember what the state machine has been told to do by the client.
Steve Jessop
@onebyone Could you please clarify what would be the purpose of the 3rd queue?
Piotr Dobrogost
I'm suggesting two queues - the one you already have, which the asynchronous I/O system is using to deliver events relating to network comms, plus a second queue, probably managed entirely by the state machine. If the state machine is instructed to perform a transition which is not possible yet, then it adds an item to this queue. Whenever it completes a transition, it checks the queue to see if there's more work it can do.
Steve Jessop
@onebyone Asynchronous I/O events are delivered to my code and not directly to the queue or machine. I have to forward them manually. Besides machine's own event loop does not queue events it receives. So there could be the case, when machine would be busy at the time network event comes in and it would miss this event. I don't want this to happen. Right now I have only one queue and it's managed entirely by me. I also manage I/O events myself. See my answer to this question where I described my current design in details.
Piotr Dobrogost
A: 

If you don't want to block, that means you don't care about the network replies. If on the other hand the replies interest you, you have to block waiting for them. Trying to design your FSM otherwise will quickly lead to your automaton's size reaching infinity.

Michael Foukarakis
I don't care about network replies in the moment I'm sending a series of events but each of these events cares as it needs information from previous ones. Because these events are sent by me you can say I care about network replies a) transitively and b) in the future :)
Piotr Dobrogost
A: 

Asking this question I already had a working design which I didn't want to write about not to skew answers in any direction :) I'm going to describe in this pseudo answer what the design I have is.

In addition to the state machine I have a queue of events. Instead of posting events directly to the machine I'm placing them in the queue. There is however problem with network events which are asynchronous and come in any moment. If the queue is not empty and a network event comes I can't place it in the queue because the machine will be stuck waiting for it before processing events already in the queue. And the machine will wait forever because this network event is waiting behind all events placed in the queue earlier.
To overcome this problem I have two types of messages; normal and priority ones. Normal ones are those send by me and priority ones are all network ones. When I get network event I don't place it in the queue but instead I send it directly to the machine. This way it can finish its current task and progress to the next state before pulling the next event from the queue of events.
It works designed this way only because there is exactly 1:1 interleave of my events and network events. Because of this when the machine is waiting for a network event it's not busy doing anything (so it's ready to accept it and does not miss it) and vice versa - when the machine waits for my task it's only waiting for my task and not another network one.

I asked this question in hope for some more simple design than what I have now.

Piotr Dobrogost
A: 

How about moving the state machine to a different thread, i. e. QThread. I would implent a input queue in the state machine so I could send queries non blocking and a output queue to read the results of the queries. You could even call back a slotted function in your main thread via connect(...) if a result of a query arrives, Qt is thread safe in this regard.

This way your state machine could block as long as it needs without blocking your main program.

drhirsch
A: 

Sounds like you just want to do a list of blocking I/O in the background.

So have a thread execute:

while( !commands.empty() )
{
  command = command.pop_back();
  switch( command )
  {
  Connect: 
    DoBlockingConnect();
    break;
  ...
  }
}
NotifySenderDone();
jyoung
1. I can't do blocking I/O in the framework I use.2. It's not about doing a series of blocking I/O operations because after every I/O operation I have to hand control over to the state machine which *decides* what to do next.
Piotr Dobrogost
1) Note that in the QT network module the "waitfor..." statements turn the non blocking IO into blocking IO. For example: socket.connectToHost(serverName, serverPort); socket.waitForConnected();2) Realize that your list of commands given have a simple 1-1 correspondance to to the blocking IO operations. This seems like you design intention was to keep the IO state machine simple and dumb, and the let the "client" for the IO state machine decide (via the command list) the I/O sequence.
jyoung