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 |
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 |
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 |
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. |
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 |
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 ================ |
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. |
Free forum by Nabble | Edit this page |