What does IPCSocket.Poll do

After a night of chasing a bug I finally found the error in the logic of my IPCSocket subclass. It was reading too much data off the buffer and made me think messages weren’t coming through. The fix was to loop in DataAvailable reading messages until none are left.

My question is why polling didn’t work? Before implementing the loop I had modified DataAvailable to properly read only one message (leaving data in the buffer) and there was a Timer calling Poll on the socket. When the Timer calls Poll I also logged bytesAvailable and see that there are bytes waiting but no DataAvailable event gets triggered.

The docs for IPCSocket.Poll simple says “Manually polls the socket.” and for IPCSocket proper “Use a Timer with a very low Period value, e.g. 1, to call the Poll method in order to have the DataAvailable event fire as quickly as possible after receiving new data.”

So when there’s 74 bytes in the buffer shouldn’t Poll raise a DataAvailable event?

Also, here’s the full code, are there any more problems or improvements I can make?

EZIPCSocket inherits IPCSocket adding send(tag, data) and event ReceiveData(tag, data). tag is used to identify the type of message and ReceiveData doesn’t fire until the whole message is across.

[code]Sub send(tag As Int32, data As MemoryBlock)

if not IsConnected then
RaiseEvent Error(1)
return
end

if data = nil or data.Size = data.SizeUnknown then
RaiseEvent Error(2)
return
end

dim fullSize As integer = 8 + data.Size

dim head As new MemoryBlock(8)

head.Int32Value(0) = fullSize
head.Int32Value(4) = tag

Write(head)
Write(data)
Flush

End Sub[/code]

[code]Sub DataAvailable()

do

if BytesAvailable < 8 then return //not enough to be a message

dim m As MemoryBlock = Lookahead

dim fullSize As integer = m.Int32Value(0)

if fullSize > m.Size then return //haven't received full message

Call Read(fullSize) //pull 1 message off buffer

dim tag As integer = m.Int32Value(4)  //pull parts out of received data
dim data As MemoryBlock = m.StringValue(8, fullSize-8)

RaiseEvent ReceiveData(tag, data)  //pass completed message along

loop

End Sub[/code]

Oh boy, you should’ve seen my post before I found my bug :slight_smile:

DataAvailable will fire when new data comes in. If there is data already in the buffer that you haven’t processed in a prior event, you won’t get another DataAvailable event. It’ll just sit there.

Thanks Tim. So Poll just brings a waiting event forward but won’t cause it.

I would suggest not putting your processing logic in the DataAvailable event. Because sockets fire their events asynchronously, you could technically have two or more of this event running at the same time, especially if you have a loop of some kind. I suggest only using this event to copy the data to a buffer and then use a timer to fire your processing code.

Thanks Greg, that’s got me looking closer at my code and I’m uncertain about this…

I’ve been picturing execution is like with a Timer where if the Action takes a long time then it takes a long time and no other events fire until Action completes. But I realized in my project, which is working now, that what you say must be happening for it to work. Let me describe the project.

A long running XojoScript is run in a helper app. The script needs to communicate progress and results back to the main app. Also the main app needs to be able to stop the running script.

So in the helper app it starts with DataAvailable firing with a full message to ‘run the script’. That’s passed to ReceiveData which initializes the script and just runs it right there. My assumption was xojo execution blocks there, in the script.Run call. To send data out the script calls context methods which Write and Flush to the very same Socket whose DataAvailable started this whole thing. I see this as just part of the stack and when the Flush call finishes execution returns to the script.

But what doesn’t make sense with my assumption is how the script is stopped. The main app sends a message to stop and the helper receives this, setting a flag on the context which the script periodically checks. How can the helper app receive anything if it’s still executing the script? I mean I know it must be what you said about being asynchronous but… I’ve never considered this could happen except in a thread. I need to think about this more. Is it basically a thread when DataAvailable fires?

Here’s an outline…

App.mSock IsA MonteIPCSocket IsA EZIPCSocket IsA IPCSocket
App.mScript IsA MonteScript IsA XojoScript

  • mSock receives a message
Sub DataAvailable()  //raises ReceiveData

Sub ReceiveData(tag As integer, data As MemoryBlock) Select Case tag Case 0 //new job to compute //initialize App.mScript App.mScript.Run //<<<<<====== this blocks until script finishes ?? Case 1 App.mScript.mDoForcedExit = true End End Sub

-Script source

[code]dim v As Double = MonteHelix()
sendResult(v) //send result to context

Function MonteHelix(…) As double
//…
for iMonte=1 to Nmonte
//…
if iMonte mod nupdate=0 then
sendProgress( str(iMonte)+“more” ) //send progress to context
if doForcedExit then Exit //check context for forced exit
end if
next
return montesumC/Nmonte
End Function
[/code]

-Context methods. These call back into the same socket to send a message to the main app

[code]Private Sub sendProgress(msg As String)
App.mSock.send(11, msg)
End Sub

Private Sub sendResult(finalValue As Double)
dim m As new MemoryBlock(8)
m.DoubleValue(0) = finalValue
App.mSock.send(10, m)
End Sub

Private Function doForcedExit() As Boolean
return mDoForcedExit
End Function
[/code]

Poll causes the socket to execute: look for incoming data, send pending data, etc. In the normal course of things, the framework’s event loop Polls each socket. If you have long-running code that is not in a thread, it will block the event loop and thereby block the sockets. In that case, you need to Poll the socket yourself to keep it active.

While sockets fire async, they do not constitute a separate thread. While it is possible to produce overlapping DataAvailable events, you would have to write code to cause it. Eg., call DoEvents or Poll from within DataAvailable.

I believe XojoScript runs synchronous to the calling code, so it will block the event loop and therefore block any data being received by the socket. However, if your messages being sent are short, they will be sent immediately. A long message would have the first part sent immediately and the rest blocked until the next event loop or poll.

If your code is executing inside DataAvailable, I would advise against Polling the socket as that could trigger another DataAvailable event.

Edit: calling Flush would cause the message to be sent. So I would expect your code to be able to send progress, but not receive any new messages until the script finishes.

As I suspected, for short messages, flush isn’t required. Write itself handles the first buffer’s worth of data.

This seems to be counter to what I’m seeing. While the script is running the Stop message from the main app does get through. Could it be that when the script sends a message back the Write or Flush does a Poll or otherwise somehow triggers DataAvailable for the Stop message?

OK, I just tested that and that’s what’s happening. Removing sendProgress from the script causes Stop to not be received until just after the script sends the result.

There’s a lot more thinking and tinkering I need to do :slight_smile:

Your ReceiveData function is coded in such a way as to deal with the reentrancy, so you should be ok with how you have it. I wasn’t aware that a flush would also check for incoming data. It’s good to know.

so would you have an example for that?