How to test blocking calls (e.g. Sockets)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

How to test blocking calls (e.g. Sockets)

Esteban A. Maringolo
Hello,

I'm trying to test operations over a pair of client/server sockets.
I'm doing this for the SPort port (pun intended) for Dolphin Smalltalk.

I'm having trouble when testing sockets, the testcase get stuck and
never passes (having to Ctrl+break it to go ahead).

I'm doing the following.

1. Instantiate a Semaphore.
2. Create a binded ServerSocket2.
3. Forking a process containing the "socket acceptance" (will block)
3a. It will asign the accepted socket to an inst var, and
3b. signal the semaphore.
4. Create a socket connected to the server socket addr&port.
5. Wait on the semaphore.
6. Do my assertions.


When I run the test case, it never reaches the 3b, and waits on 5.
If I switch the order of 3 and 4, I get the same result.

What is the best strategy to test this?
If I evaluate the statements (without the semaphore stuff), and
accepting after the connection, everything goes fine (I can do my
assertions).

What's wrong? :-/

Plus, how I should clear the state of sockets (binding, and so on), so I
can rerun the test case safely? I'm closing them all, and setting the
instVars to nil, is that enough?

All the variables used are instance variables, semaphore and
newSpServerSocket were method vars, but were promoted it to instvars
applying JIC (Just In Case) optimization ;-)

The test case (with tracing notifications is below).

Regards,

--
Esteban.


"Begin of test case".
testSocketConnectionAcceptWithNotifications
  semaphore := Semaphore new.
  serverSocket := ServerSocket2 port: 9999.
  Notification signal: 'Server socket instantiated.'.
  [
    Notification signal: 'Attempting to accept connection...'.
    newSpServerSocket := serverSocket accept.
    Notification signal: 'Connection accepted, answering socket...'.
    semaphore signal
  ] fork.

  socket := Socket port: 9999 host: 'localhost'.
  Notification signal: 'Connection successfully requested.'.
  Notification signal: 'Stopping at red light semaphore...'.
  semaphore wait.
  Notification signal: 'Green light, go ahead.'.

  self assert: newSpServerSocket notNil.
  self assert: newSpServerSocket isOpen



"TestCase Class definition"
TestCase subclass: #SpSocketTest
        instanceVariableNames: 'socket serverSocket newSpServerSocket semaphore'
        classVariableNames: ''
        poolDictionaries: ''
        classInstanceVariableNames: ''

SpSocketTest>>tearDown
        socket notNil ifTrue: [socket close].
        serverSocket notNil ifTrue: [socket close].
        newSpServerSocket notNil ifTrue: [newSpServerSocket close].
        socket := serverSocket := newSpServerSocket := nil


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Bill Dargel
Esteban A. Maringolo wrote:
> I'm having trouble when testing sockets, the testcase get stuck and
> never passes (having to Ctrl+break it to go ahead).
>

You didn't say which version of Dolphin you're using.

In Dolphin 5 (and before) the sockets use an asynchronous implementation
that requires that the main UI process be actively pumping Window's
messages in order for it to work. I'd try just using an instance of
ModalMsgLoop in place of the Semaphore.

--

Bill Dargel            [hidden email]
Shoshana Technologies
100 West Joy Road, Ann Arbor, MI 48105  USA


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Chris Uppal-3
Bill, Esteban:

> In Dolphin 5 (and before) the sockets use an asynchronous implementation
> that requires that the main UI process be actively pumping Window's
> messages in order for it to work. I'd try just using an instance of
> ModalMsgLoop in place of the Semaphore.

If you are using D5 then (unless it has been patched without my noticing) you
should fix ModalMsgLoop>>signal to read:

    signal
        loop := false.
        SessionManager inputState prod.

(That fix is in D6).

The same technique is applicable in D6 if you are using the older 'Sockets
Connection' package rather than the newer 'Dolphin Sockets' package (i.e using
Socket rather than Socket2).

    -- chris


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Esteban A. Maringolo
In reply to this post by Bill Dargel
Hello Bill:

Bill Dargel escribió:
> Esteban A. Maringolo wrote:
>> I'm having trouble when testing sockets, the testcase get stuck and
>> never passes (having to Ctrl+break it to go ahead).

> You didn't say which version of Dolphin you're using.

> In Dolphin 5 (and before) the sockets use an asynchronous implementation
> that requires that the main UI process be actively pumping Window's
> messages in order for it to work. I'd try just using an instance of
> ModalMsgLoop in place of the Semaphore.

I'm using Dolphin X6, with the Socket2 library.

Regards,

--
Esteban.


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Esteban A. Maringolo
In reply to this post by Esteban A. Maringolo
Hi myself (auto-answer):

Yo escribí:
> Hello,
> I'm having trouble when testing sockets, the testcase get stuck and
> never passes (having to Ctrl+break it to go ahead).

Thanks to S. Calvo, who noticed the missing #connect in my test case :-)
Adding this, the test case passed


> I'm doing the following.
>
> 1. Instantiate a Semaphore.
> 2. Create a binded ServerSocket2.
> 3. Forking a process containing the "socket acceptance" (will block)
> 3a. It will asign the accepted socket to an inst var, and
> 3b. signal the semaphore.
> 4. Create a socket connected to the server socket addr&port.
4b. Connect the socket :-)
> 5. Wait on the semaphore.
> 6. Do my assertions.

> When I run the test case, it never reaches the 3b, and waits on 5.
> If I switch the order of 3 and 4, I get the same result.
Now it works.

It get blocked some times, when clicking repeatedly (like mad) the
"run case" button.

This question is still open:

> Plus, how I should clear the state of sockets (binding, and so on), so I
> can rerun the test case safely? I'm closing them all, and setting the
> instVars to nil, is that enough?

Regards,
--
Esteban.


"Begin of working test case".
testSocketConnectionAcceptWithNotifications
   semaphore := Semaphore new.
   serverSocket := ServerSocket2 port: 9999.
   Notification signal: 'Server socket instantiated.'.
   [
     Notification signal: 'Attempting to accept connection...'.
     newSpServerSocket := serverSocket accept.
     Notification signal: 'Connection accepted, answering socket...'.
     semaphore signal
   ] fork.

   socket := Socket port: 9999 host: 'localhost'.
   socket connect. "<-- THIS WAS MISSING"
   Notification signal: 'Connection successfully requested.'.
   Notification signal: 'Stopping at red light semaphore...'.
   semaphore wait.
   Notification signal: 'Green light, go ahead.'.

   self assert: newSpServerSocket notNil.
   self assert: newSpServerSocket isOpen


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Chris Uppal-3
Esteban,

> It get blocked some times, when clicking repeatedly (like mad) the
> "run case" button.
>
> This question is still open:
>
> > Plus, how I should clear the state of sockets (binding, and so on), so I
> > can rerun the test case safely? I'm closing them all, and setting the
> > instVars to nil, is that enough?

You may hit problems with the SO_REUSEADDR setting.  I'm a bit hazy on the
details (it's a long time since I played with sockets much), but I think
there's a kernel/TCP-level timeout before you can re-bind to a given port (the
idea is that you don't want to receive packets on that port which were sent to
the previous user).

I don't know whether that is affecting you (it seems unlikely since I think the
delay is in the order of several seconds or more), but it might be.

The SO_REUSEADDR isn't exposed by the Dolphin sockets implementation(s) -- none
of the "minor" options are.  The following two methods are copied from Steve
Waring's Swazoo port to D(something < 6).

Incidentally, there are also subtleties about the graceful shutdown of TCP/IP
sockets which are not properly exposed in Dolphin (especially if you want to
close down the send half of the socket but not the receive). Just calling
#close works, but is rude at the protocol level.  I'm pretty sure that has no
effect on your test, but it may be an issue for SPport itself.

As an aside: I'm not convinced that SUnit is a suitable framework for starting
and stopping servers.  TCP/IP isn't set up on the assumption that servers will
pop up and die very frequently.  I would prefer (if I were doing this at all),
to start the server outside the testing framework, and then either run clients
against it from within SUnit, or -- if I want to test server-side code under
active development -- to insert the target code into the server, test it and
then remove it, all without restarting the server.

    -- chris

================
ServerSocket>>setREUSEADDR
 | result v |
 #swAdded.
 result := WSockLibrary default
    setsockopt: self asParameter
    level: 65535
    optname: 4
    optval: (v := DWORD fromInteger: 1) asParameter
    optlen: v byteSize. "SOL_SOCKET      0xffff" "SO_REUSEADDR    0x0004 /*
allow local address reuse */"
 result = 0 ifFalse: [self error]
================
ServerSocket class>>portReuseAddr: anIntegerPort backlog: anInteger
 "Answer a new instance of the receiver set up to listen on anIntegerPort.
 Setting the REUSEADDR option means that the server can start listening
 if there is a socket with the same host/port in the TIME_WAIT state.
 Practically it means that we can stop and restart the server without
 waiting a couple of minutes)"

 #swAdded.
 ^(self new)
  preBindPort: anIntegerPort;
  setREUSEADDR;
  bind;
  listen: anInteger;
  yourself
================


Reply | Threaded
Open this post in threaded view
|

Re: How to test blocking calls (e.g. Sockets)

Esteban A. Maringolo
Chris Uppal escribió:

> Esteban,
>
>> It get blocked some times, when clicking repeatedly (like mad) the
>> "run case" button.
>>
>> This question is still open:
>>
>>> Plus, how I should clear the state of sockets (binding, and so on), so I
>>> can rerun the test case safely? I'm closing them all, and setting the
>>> instVars to nil, is that enough?
>
> You may hit problems with the SO_REUSEADDR setting.  I'm a bit hazy on the
> details (it's a long time since I played with sockets much), but I think
> there's a kernel/TCP-level timeout before you can re-bind to a given port (the
> idea is that you don't want to receive packets on that port which were sent to
> the previous user).
>
> I don't know whether that is affecting you (it seems unlikely since I think the
> delay is in the order of several seconds or more), but it might be.

The problem was with the forked process, I never terminated it in the
TestCase tear down phase.

I'm using Sockets2, so I don't know if it uses SO_REUSEADDR or not.

> As an aside: I'm not convinced that SUnit is a suitable framework for starting
> and stopping servers.  TCP/IP isn't set up on the assumption that servers will
> pop up and die very frequently.  I would prefer (if I were doing this at all),
> to start the server outside the testing framework, and then either run clients
> against it from within SUnit, or -- if I want to test server-side code under
> active development -- to insert the target code into the server, test it and
> then remove it, all without restarting the server.

I'm testing the sockets :-/, so I have to test them "inside".


Thank you for your comprehensive explanation!

Regards,

--
Esteban.