wait for reply without app.doEvents

I have a tcpSocket, and a method within that class called ‘sendPacket’. It basically sends data to a remote system, then WAITS for a corresponding reply, then returns the reply (ie. if sendPacket(“what is 4+4?”, theResult) then…). I’ve been using something along the lines of (where tcpSocket.receiveMessage would populate a property of memoryBlock called inputBuffer):

[code]do until (inputBuffer.size <> 0) or (me.readError) or (me.writeError) or (ticks > timeOut)
app.doEvent
loop

if (me.readError) or (me.writeError) or (ticks > timeOut) then
return false
else
theResult = inputBuffer
return true
end if[/code]

The problem is, if the connection is terminated while the do/loop is going, I get a stackOverflowException when trying to re-initialize the port for listening. I’ve narrowed it down to the do/loop definitely causing the issue (more specifically, the app.doEvents). What is the best way to do what I’m needing w/o the app.doEvents? I want to call a method… and WAIT for content to fill a buffer (and do other various checks and balances on the buffer) BEFORE returning/exiting the method with a reply. During this time, the app needs to wait to move on until the previous sendPacket is acknowledged with a reply.

I, personally and with my lack of alternatives, am unfamiliar how to do what I’m needing with the infamous app.doEvents (which I always heard would cause weird issues, but have never actually run into any until now. Now I’m determined like hell to move away from it for good).

Not sure why the post quoted itself… sorry about that.

I’d move the main code into the DataAvailable event and use a Timer to check for the timeout. In fact, I’d subclass TCPSocket to include that Timer internally.

But if you really want to do it procedurally, use TCPSocket.Poll instead of DoEvents. And never use DoEvents outside a console app.

My best advice is to rethink your approach and go about it asynchronously. It feels completely unnatural until you’ve done it a few times. (That would be the DataAvailable method that Kem suggested.) Alternately, you could move your procedural code into a Thread and sleep the thread instead of calling DoEvents. Using Poll instead of DoEvents will block the rest of your app. If that’s acceptable, then it may be the easiest solution.

Unfortunately, this is going to require a pretty big learning curve for me… which I don’t currently have the time for. I need a solution for this pretty quickly. Is it possible to run a method in a separate thread, and still being able to pass a return?

BTW, thanks for the help guys… appreciate it.

Why won’t Poll work for you then?

Doing that seems to freeze up the app. This is a console app, which is why I was less reluctant to use app.doEvents… but it still seems to be causing me issues. Actually… this may work, as I have processes in the main thread running which can be put into threads themselves. I’m obviously a bit lost and confused… but will try to figure something out. o.O

Could you use the native TCPSocket events for your needs if the timeout wasn’t a factor? In other words, send your packet, then handle the response in the DataAvailable or Error event?

Not sure. Let me try to explain what I’m doing w/ some examples…

[code]function sendPacket(responseExpected as boolean, targetContent as memoryBlock, timeOut as uInt64, byRef replyContent as memoryBlock) as boolean

dim processedPacket as memoryBlock
dim expireTime as uInt64

expireTime = (timeOut * 60) + ticks '[timeOut] in seconds

expectingResponse = responseExpected
responsePacket = NULL_STRING
receivedAck = false

if (targetContent = NULL_STRING) or (targetContent.size = 0) or (targetContent.size > 999999) then
errorFramework.logError(ERROR_SILENT, system.logLevelAlert, “coreTcp/sendPacket - invalid content requirements”)
expectingResponse = false
return false
end if

if not constructPacket(false, responseExpected, targetContent, processedPacket) then 'error constructing packet
errorFramework.logError(ERROR_SILENT, system.logLevelAlert, “coreTcp/sendPacket - packet construct error”)
return false
else 'packet constructed successfully; send constructed packet to remote connection
me.sendMessage(0, processedPacket)
idleTimer.reset 'sent packet; reset idle timer
end if

// - wait for successful send, error, or timeout
do until (me.bytesLeftToSend = 0) or (me.readError) or (me.writeError) or (ticks > expireTime)
app.doEvents()
loop

if (me.writeError) or (me.readError) then 'tcp socket read/write error
errorFramework.logError(ERROR_SILENT, system.logLevelAlert, “coreTcp/sendPacket - readError/writeError [send]”)
expectingResponse = false
return false
elseif (ticks > expireTime) then 'timeout flagged; specified by [timeOut]
errorFramework.logError(ERROR_SILENT, system.logLevelAlert, “coreTcp/sendPacket - timeOut [send]”)
expectingResponse = false
return false
elseif (me.bytesLeftToSend = 0) then 'packet successfully sent
if responseExpected then 'packet expects a response from remote connection
// - wait for successful response, error, or timeout
do until (responsePacket <> NULL_STRING) or (me.readError) or (me.writeError) or (ticks > expireTime)
app.doEvents()
loop

  if (me.writeError) or (me.readError) then 'tcp socket read/write error
    errorFramework.logError(ERROR_SILENT, system.logLevelAlert, "coreTcp/sendPacket - readError/writeError [response]")
    expectingResponse = false
    return false
  elseif (ticks > expireTime) then 'timeout flagged; specified by [timeOut]
    errorFramework.logError(ERROR_SILENT, system.logLevelAlert, "coreTcp/sendPacket - timeOut [response]")
    expectingResponse = false
    return false
  elseif (responsePacket <> NULL_STRING) then 'response successfully received
    expectingResponse = false
    replyContent = responsePacket 'return response data
    return true
  end if
else 'packet only expects an acknowledgement from remote connection
  expectingResponse = false
  replyContent      = NULL_STRING
  
  do until (receivedAck = true) or (me.readError) or (me.writeError) or (ticks > expireTime)
    app.doEvents()
  loop
  
  if (me.writeError) or (me.readError) then 'tcp socket read/write error
    errorFramework.logError(ERROR_SILENT, system.logLevelAlert, "coreTcp/sendPacket - readError/writeError [acknowledge]")
    return false
  elseif (ticks > expireTime) then 'timeout flagged; specified by [timeOut]
    errorFramework.logError(ERROR_SILENT, system.logLevelAlert, "coreTcp/sendPacket - timeOut [acknowledge]")
    return false
  elseif (receivedAck = true) then 'acknowledgement successfully received
    return true
  end if
end if

end if

end function[/code]

to send a packet in other areas of my code, this is what I use…

if tcpFramework.sendPacket(true, "SEND ME SOMETHING", 15, tcpResponse) then tcpFramework.sessionId = targetSessionId + outgoingPacket 'set [:coreTcp/sessionId] with constructed 18-digit value else // - could not get response; handle error here end if

The tcpFramework (which is an easyTcpSocket) populates the ‘responsePacket’ when the remote system returns a value. When it does, this method passes that byRef to ‘replyContent’.

It’s important in this process that data is returned with that sendPacket (unless an error occurs, which I handle) before continuing on to the next line of code… (which is likely more sendPacket commands, checks & validations, etc.). It has to be processed in order.

I see. It looks like using the events would require a rethinking of the design.

What about just using Poll and App.YieldToNextThread? Since you want the current thread to stop until it get a response, at least that would let your other threads continue.

      do until (receivedAck = true) or (me.readError) or (me.writeError) or (ticks > expireTime)
        App.YieldToNextThread()
        me.Poll()
      loop

That actually worked perfectly, and no stackOverFlow error. Appreciate the help, Kem!

That would seem to indicate that this code is already in a thread. If so, the Poll is probably not necessary.

Well, I ran into another problem. Turns out, it wasn’t the app.doEvents causing the error. The Error event in the easytcpSocket gets called about 40 times when the connection is terminated…

[code] const NO_ERROR = 0
const OPEN_DRIVER_ERROR = 100
const LOST_CONNECTION = 102
const NAME_RESOLUTION_ERROR = 103
const ADDRESS_IN_USE_ERROR = 105
const INVALID_STATE_ERROR = 106
const INVALID_PORT_ERROR = 107
const OUT_OF_MEMORY_ERROR = 108

print “something went wrong!”

if code = LOST_CONNECTION then
idleTimer.enabled = false 'disable connection idle time-out; connection lost

if (not me.isConnected) then
  tcpFramework.listen          'enable socket listener
end if

end if[/code]

When I run this… “something went wrong!” prints out about 40 times, and I get the ‘stackOverFlow’ error on the ‘tcpFramework.listen’ line (which if this line is being called 40 times in a row, no wonder!). What would cause this?

What’s even more frustrating, is this doesn’t happen every time… only about half the time! Argh!

Try moving the Listen call out of the event. Use a short-period timer and call listen from there. The Easy-socket probably needs a chance to clean itself up.

Also, is your DoEvents code in a thread? If so, Xojo has already translated it to a benign yield for you.

I got rid of the doEvent calls all together, except in the do/loop at the end of the Run event of the app (to keep the application going). So, it was working fine with Kem’s suggestion… until just a bit ago (so now I’m concluding that maybe it wasn’t the doEvent causing the issue all along). Instead, I’m thinking it may be a bug in the socketClass… as I don’t know what would cause the Error on a termination to call 4 dozen times in a row like that. I did confirm that the code being returned is 102 on each of those instances, so it’s not something I’m doing otherwise causing the error event to initiate that many times in a row so quickly.

As always, appreciate your help Tim.

I agree with Tim that moving Listen out of the Error event into a Timer or elsewhere is a better move.

Also, what is idleTimer, and what does setting it’s Enabled to False do?

idleTimer will disconnect the connection within its action event if no data is sent or received within the period time-frame. On receiveMessage and sendPacket (my custom method), I idleTimer.reset.

On another note, moved the Listen out of the Error event and into a Timer as suggested and everything is once again working fine. I’ve ran it through some different scenarios, and no problems. I think it’s finally solved… appreciate the help you two!