Java-Gaming.org    
Featured games (91)
games approved by the League of Dukes
Games in Showcase (577)
games submitted by our members
Games in WIP (498)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Selector not blocking on select  (Read 3405 times)
0 Members and 1 Guest are viewing this topic.
Offline Jannick

Senior Newbie




Java games rock!


« Posted 2004-02-25 20:22:15 »

Hi, I finally got around to play with some nio code again, but of course things had to stop working before I could get anywhere  :-/  To make it easier to overlook i've put together some code containing of only the basic stuff, but where the problem also exists.

If I connect to my code using fx telnet everything works fine. If I then try to make a connection from macromedia flash and continue to start up the flash script (which means closing the connection and opening a new one fast) my selector suddenly starts to return immidiatly on select, returning 0 selectionkeys. Sometime this condition is even created the first time I connect from flash. My orriginal code connected to another server (and went bananas when the connections were dropped quick), but the behavior has been similar with this test code.

Once the selector has started returning immidiatly it will continue until a new connection is made, after when it will enter into normal blocking operation.  Another thing worth noting is that if I dont write out anything to the socket then its not possibel to create this behavior.

Hope someone here can help me figure it out, cause Im pretty empty for ideas atm =) Ill post the code in seperate posts to make it easier to overlook.
Offline Jannick

Senior Newbie




Java games rock!


« Reply #1 - Posted 2004-02-25 20:23:33 »

ConnectionTenant: A "controller" for each connection, just a seperate outputbuffer in this example:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
import java.nio.*;
import java.nio.channels.*;

public class ConnectionTenant{

    private SelectionKey sk;
    private ByteBuffer outBuffer;
    private ConnectionWorker cw;

    public ConnectionTenant(ConnectionWorker cw){
      this.cw = cw;

      String welcome = "Hej med dig";
      outBuffer = ByteBuffer.allocate(500);

      byte[] outArr = welcome.getBytes();
      outBuffer.put(outArr);
    }


    public void setSelectionKey(SelectionKey sk){
      this.sk = sk;
      cw.addWriteInterest(sk);
    }
   
   
    public boolean hasData(){
      return (outBuffer.position() > 0);
    }

    public ByteBuffer getOutputBuffer(){
      outBuffer.flip();
      return outBuffer;
    }

    public void onWritePerformed(){
      outBuffer.compact();
    }

    public boolean isRequestingShutdown(){
      return false;
    }

    public void onShutdown(){
      System.out.println("[ConnectionTenant:onShutdown()]");
    }
   

}
Offline Jannick

Senior Newbie




Java games rock!


« Reply #2 - Posted 2004-02-25 20:24:18 »

ConnectionWorker: The selector object

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
import java.nio.*;
import java.nio.channels.*;
import java.io.*;
import java.util.*;

public class ConnectionWorker implements Runnable{

    private FIFOQueue newConnections = new FIFOQueue(10);
    private FIFOQueue writeRequests = new FIFOQueue(10);
    private Selector selector;
    private ByteBuffer readBuffer;
    private Thread internalThread;
   
    public ConnectionWorker() throws IOException{
      selector = Selector.open();
      readBuffer = ByteBuffer.allocateDirect(1000);

      internalThread = new Thread(this);
      internalThread.start();
    }


    public synchronized void addSocketChannel(SocketChannel sc){
      newConnections.addObject(sc);
      selector.wakeup();
    }

    public synchronized void addWriteInterest(SelectionKey sk){
      writeRequests.addObject(sk);
      selector.wakeup();
    }

    private void closeChannel(SocketChannel sc){
      if (sc == null) return;

      SelectionKey sk = sc.keyFor(selector);
      if (sk != null){
          if (sk.attachment() != null){
            ((ConnectionTenant)sk.attachment()).onShutdown();
          }
          sk.cancel();
      }

      try{
          sc.socket().close();
      }catch (IOException e){
          e.printStackTrace();
      }

      try{
          sc.close();
      }catch (Throwable t){
          t.printStackTrace();
      }

    }

    private void processNewConnections(){
      for (Object o = newConnections.getObjectNow(); o != null; o = newConnections.getObjectNow()){
          SocketChannel sc = (SocketChannel)o;

          if(!sc.isConnected()){
            return;
          }
         

          try{
            System.out.println("[ConnectionWorker] Registering new connection");

            sc.configureBlocking(false);
            ConnectionTenant ct = new ConnectionTenant(this);
            SelectionKey sk = sc.register(selector, SelectionKey.OP_READ, ct);
            ct.setSelectionKey(sk);

          }catch (Exception e){
            System.out.println("Error caught in processNewConnections(). Printing stack trace: ");
            e.printStackTrace();
           
            closeChannel(sc);
          }
      }

    }//end processNewConnections()


    private void processWriteInterests(){
      for (Object o = writeRequests.getObjectNow(); o != null; o = writeRequests.getObjectNow()){
          SelectionKey sk = (SelectionKey)o;
          sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
      }

    }//end processWriteInterests()
   


   

    public void run(){
      int nSelKeys = 0;
      System.out.println("[ConnectionWorker] Starting service");

      while (true){
          processNewConnections();
          processWriteInterests();
         
          try{
            nSelKeys = selector.select();

          }catch (Exception e){
            e.printStackTrace();
            return;
          }

          System.out.println("[ConnectionWorker] Selected " + nSelKeys + " keys");
          if (nSelKeys == 0) continue;
         
          Set keys = selector.selectedKeys();
          Iterator i = keys.iterator();

          while (i.hasNext()){
            SelectionKey sk = (SelectionKey)i.next();
            i.remove();

            processKey(sk);
          }
      }

    }//end run()


    private void processKey(SelectionKey sk){
      if (!sk.isValid()){
          System.out.println("[DEBUG] Invalid key");
          return;
      }
     
      if (sk.isReadable()){
          if (!processRead(sk)) return;
      }

      if (sk.isWritable()){
          processWrite(sk);
      }
     
    }//end processKey()


    private boolean processRead(SelectionKey sk){
      readBuffer.clear();
      ReadableByteChannel rbc = (ReadableByteChannel)sk.channel();
      int numBytesRead = 0;
     
      try{
          numBytesRead = rbc.read(readBuffer);

      }catch (Exception e){
          e.printStackTrace();
          closeChannel((SocketChannel)sk.channel());
          return false;
      }
     

      if (numBytesRead < 0){
          closeChannel((SocketChannel)sk.channel());
          return false;
      }

      readBuffer.flip();
      //Discard data in demo


      return true;
    }//end processRead()



    private void processWrite(SelectionKey sk){
      ConnectionTenant ct = (ConnectionTenant)sk.attachment();
     
      if(ct.hasData()){
          WritableByteChannel wbc = (WritableByteChannel)sk.channel();
         
          try{
            ByteBuffer writeBuffer = ct.getOutputBuffer();
            wbc.write(writeBuffer);
           
            ct.onWritePerformed();

          }catch (Exception e){
            e.printStackTrace();
           
            closeChannel((SocketChannel)sk.channel());
          }


      }else{
          sk.interestOps(sk.interestOps() & (~SelectionKey.OP_WRITE));

          if (ct.isRequestingShutdown()){
            closeChannel((SocketChannel)sk.channel());
          }
      }

    }//end processWrite()

}
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Jannick

Senior Newbie




Java games rock!


« Reply #3 - Posted 2004-02-25 20:25:43 »

Should be irrelevant, but heres the ConnectionListner:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
import java.util.*;
import java.net.*;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;


public class ConnectionListener implements Runnable{


    private Selector selector;
    private Thread internalThread;
    private ConnectionWorker cw;
    private String host;
    private int port;

    public ConnectionListener(String host, int port) throws IOException{
      this.host = host;
      this.port = port;

      selector = Selector.open();
     
      ServerSocketChannel ssc = ServerSocketChannel.open();
      ssc.configureBlocking(false);
      ssc.socket().bind(new InetSocketAddress(host, port));
      ssc.register(selector, SelectionKey.OP_ACCEPT);
     
      cw = new ConnectionWorker();

      internalThread = new Thread(this);
      internalThread.start();

    }



    public void run(){
      int nSelKeys = 0;
      System.out.println("[ConnectionListener] Starting service");

      while (true){
         
          try{
            nSelKeys = selector.select();

          }catch (Exception e){
            e.printStackTrace();
            return;
          }
         
          if (nSelKeys == 0) continue;
         
          Set keys = selector.selectedKeys();
          Iterator i = keys.iterator();

          while(i.hasNext()){
            SelectionKey sk = (SelectionKey)i.next();
            i.remove();

            processKey(sk);
          }

      }

    }//end run()



    private void processKey(SelectionKey sk){
      if(!sk.isValid()){
          System.out.println("[ConnectionListner] Invalid key");
          return;
      }

      try{
          ServerSocketChannel ssc = (ServerSocketChannel)sk.channel();
          SocketChannel sc = ssc.accept();

          cw.addSocketChannel(sc);

      }catch (Exception e){
          e.printStackTrace();
          return;
      }

    }//end processKey()



    public static void main(String[] args) throws Exception{
      if(args.length < 2){
          System.out.println("Syntax: ConnectionListner <host> <port>");
          System.exit(0);
      }

      int port = 5000;
      String host = args[0];
     
      try{
          port = Integer.parseInt(args[1]);
      }catch (NumberFormatException nfe){}

      System.out.println("host: " + host);
      System.out.println("port: " + port);

      ConnectionListener cl = new ConnectionListener(host, port);
    }





}
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #4 - Posted 2004-02-26 09:43:53 »

Firstly, are you using 1.4.2? If not, no-one is likely to care. 1.4.0 and 1.4.1 do not work with NIO: they have too many major bugs, many with no workaround. They are also *very* platfrom-dependent, so only someone with your exact OS can help  you.

Assuming you're using 1.4.2 or above, I'm afraid that's far too much code to wade through without any comments. The execution path is definitely non-obvious, and so to work out what you're doing when is a difficult task for anyone other than you to do quickly. This is why I spent 30 seconds looking at your code and moved on (having learnt nothing at all in 30 seconds Sad ), assuming someone else with more time would have a look for you.

Since no-one else has replied... If you can put together all the lines - in sequence - that handle or interact with your Selector, I'll have another look. This should be about 20-40 lines of code. No methods, please, and comments every few lines to say what you're about to do in the next X lines (e.g. every 2-5 lines is usually about right for selector interaction) would help immensely. Often it's possible to spot the problem just by reading the comments.


malloc will be first against the wall when the revolution comes...
Offline Jannick

Senior Newbie




Java games rock!


« Reply #5 - Posted 2004-02-26 11:43:18 »

Im using 1.4.2 and have tried it on both windows 2000 and windows xp. Ill just get the important stuff typed out for the selector loop.
Offline Jannick

Senior Newbie




Java games rock!


« Reply #6 - Posted 2004-02-26 12:03:35 »

The basic princip is that only the selector thread does selector related operations. New connections are added to a queue and the selector thread then registers them as part of the select loop:

1  
2  
3  
4  
5  
6  
7  
8  
  
//Executed for all new connections
//ConnectionTenant is a buffer holding object
sc.configureBlocking(false);
ConnectionTenant ct = new ConnectionTenant(this);
SelectionKey sk = sc.register(selector, SelectionKey.OP_READ, ct);
ct.setSelectionKey(sk);
 


Similar, if the connection tenant wants to write, it adds a request to a queue, as part of the select loop the thread then modifies interestOps with this:

1  
2  
 //Executed for each connection that wants to write
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);


The SelectionKeys returned by selector.select(); is checked for valid operations by this code:


1  
2  
3  
4  
5  
6  
7  
 if (sk.isReadable()){ 
     if (!processRead(sk)) return;
 }
 
 if (sk.isWritable()){
     processWrite(sk);
 }


If its readable the reading is done with this:

1  
2  
3  
4  
//Using a shared direct bytebuffer for reading in the available data. In this example the data is never passed on/used
readBuffer.clear();
 ReadableByteChannel rbc = (ReadableByteChannel)sk.channel();
 int numBytesRead = 0;


And if its writeable:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
//ConnectionTenant holds a writebuffer for the connection. Get the buffer and write from it. ConnectionTenant takes care of preparing buffer to be written before returning it.
ConnectionTenant ct = (ConnectionTenant)sk.attachment();
 
 if(ct.hasData()){
     WritableByteChannel wbc = (WritableByteChannel)sk.channel();
     

  ByteBuffer writeBuffer = ct.getOutputBuffer();
  wbc.write(writeBuffer);
   
  ct.onWritePerformed();



The initial handling of the Set returned by the select operation is handeled by this code:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
//If the selector returned any keys iterate through the key set and toss each of them off to processing
    if (nSelKeys == 0) continue;
     
     Set keys = selector.selectedKeys();
     Iterator i = keys.iterator();
 
     while (i.hasNext()){
  SelectionKey sk = (SelectionKey)i.next();
  i.remove();
 
  processKey(sk);
     }


Hope it made it a bit more clear
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #7 - Posted 2004-02-26 12:33:17 »

Quote
my selector suddenly starts to return immidiatly on select, returning 0 selectionkeys. Sometime this condition is even created the first time I connect from flash. My orriginal code connected to another server (and went bananas when the connections were dropped quick), but the behavior has been similar with this test code.


The only way it will return 0 selectionkeys is if you called wakeup() (you're not using interrupt() AFAICS).

Try checking your code that calls wakeup() and see how it could end up being invoked more often than you expect...?

EDIT: Here's a guess: you're calling wakeup() once too many times for each time you intend to call it. If you check the API docs you'll see that they "stack up" - so that if you call it twice when a select is blocking, the first one will wake up the selector, and the second one will be queued so that the next "select" returns as soon as it's called.

malloc will be first against the wall when the revolution comes...
Offline Jannick

Senior Newbie




Java games rock!


« Reply #8 - Posted 2004-02-26 12:36:19 »

Ive tried a System.out.println("...") before both wakeups, and its never called. The selector just returns immidiatly on .select(); until a new connections is added.

It keep returning on select (produce 4mb logfile in 10-15 seconds) so is not because wakeups is queued.
Offline cknoll

Junior Member




Flame On!


« Reply #9 - Posted 2004-02-26 14:25:49 »

Question: if you put non-blocking channels in a selector, will they automatically appear 'ready' when you do a select?  I thought the point of selectors was you register blocking channels and when one of them 'unblocks' it will notify the selector?  Or something like that...

I'm referring to this block of code:
1  
2  
3  
4  
5  
6  
 //Executed for all new connections  
//ConnectionTenant is a buffer holding object
sc.configureBlocking(false);  
ConnectionTenant ct = new ConnectionTenant(this);  
SelectionKey sk = sc.register(selector, SelectionKey.OP_READ, ct);  
ct.setSelectionKey(sk);  


-Chris
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #10 - Posted 2004-02-26 15:47:14 »

Quote
Ive tried a System.out.println("...") before both wakeups, and its never called. The selector just returns immidiatly on .select(); until a new connections is added.


Assuming there are no keys, and if you can comment out the wakeups, and there are no other wakeups or interrupts in another part of your code (do a search/replace for them), AND if you aren't silently handling an exception, then you probably have a bug. C.f. the API docs for info on what select does, and assuming the above it looks to me (I just re-read it to be sure, but maybe I've missed something so check yourself) like the contract is being broken.

Cut out as much code as possible whilst preserving the problem, and get ready to file a bug report....but paste here if you can get it down real small (inline most methods, and aim for under 60 LOC if you can) and maybe some subtle mistake will become obvious Smiley.

malloc will be first against the wall when the revolution comes...
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #11 - Posted 2004-02-26 15:49:49 »

Quote
Question: if you put non-blocking channels in a selector, will they automatically appear 'ready' when you do a select?  I thought the point of selectors was you register blocking channels and when one of them 'unblocks' it will notify the selector?  Or something like that...


They automatically appear as "ready" for whichever of the operations you said you wanted to be notified of IFF that operation has data (or something; nb there is the undocumented notify-of-disconnect that counts as "data" here) ready.

But they don't *disappear* unless you manually remove them.

But he says *nothing* is appearing as ready - there are no selection keys in the selector's set.

malloc will be first against the wall when the revolution comes...
Offline Jannick

Senior Newbie




Java games rock!


« Reply #12 - Posted 2004-02-26 17:16:39 »

I've tried inlining as much as possibel, and made a lil discovery. If I register the connection for OP_WRITE right from the beginning then I cant create the problemo, so seems it could be related to the sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE) call. Ill get some more tests done on this before posting more code, cant really get it down to 60loc so far.

Another little note is that it seems much easier to create this condition when run the flash client on another computer, so might be a time factor involved.
Offline cknoll

Junior Member




Flame On!


« Reply #13 - Posted 2004-02-26 17:19:21 »

Ok, well, even if that is the case, I'm not sure if you want to set these sockets up as non-blocking because that's what the selector is for: to block only when all channels in the selector would block.  So change the line of code to this:

1  
2  
 //ConnectionTenant is a buffer holding object
sc.configureBlocking(true);


and see if that gives you the desired result.  If it fixes the problem but what blahablhbalhahalahaha says is correct, then that's probably an issue that needs to be reported.

-Chris
Offline Jannick

Senior Newbie




Java games rock!


« Reply #14 - Posted 2004-02-26 17:22:22 »

If I leave them in blocking mode (default for new connections) then Ill end up with blocking read and write calls and that pretty much kills the idea.
Offline Jannick

Senior Newbie




Java games rock!


« Reply #15 - Posted 2004-02-26 18:07:47 »

Okay I've figured out what causes the problem, dunno if you would classify it as a bug.

In my original test code I made a ConnectionTenant object for each connection, which would hold an internal write buffer. The tenant would ready some welcome/ackknowledge data in a bytebuffer, and on setSelectionKey(SelectionKey sk) it would call back to add itself to a "wantToWrite" queue. Effective result of this was that the selectors own thread called back into a method that called selector.wakeup.

In the current version of my code I dont even write (and dont have to read either, just got the code for it to handle disconnects):

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
          //Register the new connections in queue
         for (Object o = newConnections.getObjectNow(); o != null; o = newConnections.getObjectNow()){
            SocketChannel sc = (SocketChannel)o;
            if(!sc.isConnected()) return;
            
            try{
                System.out.println("[ConnectionWorker] Registering new connection");
                sc.configureBlocking(false);
                SelectionKey sk = sc.register(selector, SelectionKey.OP_READ);
                System.out.println("INNER WAKEUP");
                //selector.wakeup();

            }catch (Throwable tr){
                tr.printStackTrace();
                closeChannel(sc);
            }
          }


The code is placed before selector.select() in the select loop. If I comment out selector.wakeup() everything runs as normal, but if its not the selector will start to return immidiatly on select. The wakeup placed in the code snippet will only be called for every new connection, so its not like it queue a new "wakeup request" up each loop. Ive made test where wakeup() in the select loop is called one time, but the selector keep non-blocking select until a new connection is made.

An imporant note is that if I connect to the server with telnet from my workstation (where I also run the server) I cant create the bug. The first time (most of the time, sometimes take 2-3 connections) I connect with telnet from my "test machine" (stands right next to workstation, both connected to same switch) it will start its non-blocking select. I dont know enough about low lvl io to say anything based on this, but it obviously has an effect that its a "real" network connection.
Offline Jannick

Senior Newbie




Java games rock!


« Reply #16 - Posted 2004-02-26 18:11:06 »

Heres my current test code, got it somewhat down in length.

addSocketChannel(SocketChannel sc).. is called by the ConnectionListener and is the only place any other thread calls any method in this object.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
public class ConnectionWorker implements Runnable{

    private FIFOQueue newConnections = new FIFOQueue(10);
    private Selector selector;
    private Thread internalThread;
    private ByteBuffer readBuffer;

    public ConnectionWorker() throws IOException{
      selector = Selector.open();
      readBuffer = ByteBuffer.allocateDirect(1000);

      internalThread = new Thread(this);
      internalThread.start();
    }


    public void addSocketChannel(SocketChannel sc){
      newConnections.addObject(sc);
      System.out.println("Calling wakeup()!! new socket added to queue");
      selector.wakeup();
    }


    private void closeChannel(SocketChannel sc){
      if (sc == null) return;

      SelectionKey sk = sc.keyFor(selector);
      if (sk != null) sk.cancel();

      try{
          sc.close();
      }catch (Throwable t){
          t.printStackTrace();
      }

    }


    public void run(){
      int nSelKeys = 0;
      System.out.println("[ConnectionWorker] Starting service");

      while (true){
          //Register the new connections in queue
         for (Object o = newConnections.getObjectNow(); o != null; o = newConnections.getObjectNow()){
            SocketChannel sc = (SocketChannel)o;
            if(!sc.isConnected()) continue;
            
            try{
                System.out.println("[ConnectionWorker] Registering new connection");
                sc.configureBlocking(false);
                SelectionKey sk = sc.register(selector, SelectionKey.OP_READ);
                System.out.println("INNER WAKEUP");
                //selector.wakeup();

            }catch (Throwable tr){
                tr.printStackTrace();
                closeChannel(sc);
            }
          }

          try{
            nSelKeys = selector.select();

          }catch (Exception e){
            e.printStackTrace();
            return;
          }

          System.out.println("[ConnectionWorker] Selected " + nSelKeys + " keys");
          if (nSelKeys == 0) continue;
          
          Set keys = selector.selectedKeys();
          Iterator i = keys.iterator();

          while (i.hasNext()){
            try{
                SelectionKey sk = (SelectionKey)i.next();
                i.remove();


                if (sk.isReadable()){
                  //Read so that it wont keep returning key
                 //In case test klient sends data
                 readBuffer.clear();
                  int readBytes = ((SocketChannel)sk.channel()).read(readBuffer);
                  if (readBytes < 0) closeChannel((SocketChannel)sk.channel());
                }

            }catch (Throwable t){
                t.printStackTrace();
                continue; //next key
           }  

          }//end while (i.hasNext())

      }

    }//end run()

}
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #17 - Posted 2004-02-26 18:11:46 »

Quote
Ok, well, even if that is the case, I'm not sure if you want to set these sockets up as non-blocking because that's what the selector is for: to block only when all channels in the selector would block.  So change the line of code to this:


!!! what do you see as the point of the method call configueBlocking() then, if it's not to enable non-blocking mode?

malloc will be first against the wall when the revolution comes...
Offline blahblahblahh

JGO Coder


Medals: 1


http://t-machine.org


« Reply #18 - Posted 2004-02-26 18:28:11 »

Quote
Okay I've figured out what causes the problem, dunno if you would classify it as a bug.

...Effective result of this was that the selectors own thread called back into a method that called selector.wakeup.


So my guess was pretty close?

Quote

if its not the selector will start to return immidiatly on select. The wakeup placed in the code snippet will only be called for every new connection, so its not like it queue a new "wakeup request" up each loop. Ive made test where wakeup() in the select loop is called one time, but the selector keep non-blocking select until a new connection is made.


If you look at the way the API is designed this problem makes sense somewhat.... The design (although Sun doesn't really explain this in the docs, you can find treatises on the different ways OS's implement asynch elsewhere) is that once something happens that the Selector notices, it assumes that thing is always happening, and doesn't listen out for it to stop.

Hence the warnings to remebmer to remove keys from the set, else your channels will always appear to be readable/writable/etc from as soon as they first become so.

If the select exited with a key state-change, you can reset it by removing the keys. If it exited with a wakeup with no keys, you can't do anything to change it's internal state, so it carries on in the same state forever returning an empty key set.

I suspect there is a naive FSM inside which saves it's previous state and monitors if the state has been changed. If this is even close to true, it's very sad because it means the authors didn't have a good set of unit tests (this happens very occasionally with particular parts of the standard libs, where a group of bugs appear that show the author of a particular class was not doing much unit testing).

I believe this is definitely a bug, because I'm pretty sure that this wasn't the intended behaviour. I suggest you log a bug-report, and put in the suggested action/workaround fields something like:

 1. You could change it to *automatically* reset its status if it leaves a select with no keys, so that it won't immediately do the same thing on the next select call (i.e. fix the bug) - this is the preferred option
 2. You could add a method .reset() to Selector which does the same thing manually, so that if someone calls wakeup and gets back an empty set they can at least force it to go back to blocking - this is in case there is some reason why number 1 is undesirable. It also has the benefit of being backwards compatible.

If there's a reason why this isn't a bug, they'll probably tell you!

(don't forget to include your handy shortened code; with the code snippet they're much more likely to accept the bug, assuming you give them enough info to reproduce it!)

malloc will be first against the wall when the revolution comes...
Offline cknoll

Junior Member




Flame On!


« Reply #19 - Posted 2004-02-27 00:48:06 »

Quote

!!! what do you see as the point of the method call configueBlocking() then, if it's not to enable non-blocking mode?


I'm saying that I don't see the point in enabling non-blocking mode on a socket when using channel selectors.  Why would you?

-Chris
Offline Jannick

Senior Newbie




Java games rock!


« Reply #20 - Posted 2004-02-27 08:25:25 »

Ill try to put some client code together that can make it easier to reproduce the behavior.

If what you'r saying is correct, then isnt it impossibel to do a workaround to this problem? In theory the addNewConnections method could be called while the selector thread was out in the select loop, and had last returned a 0-set. If the new connection had then been closed before being processed it wouldnt be registered. Without the register() call (which seems to reset internal state) the selector would be in the same situation as in my test code. It of course takes a good portion of bad luck to have the thread scheduler "pause" the selector thread out in the select loop, but thats not something you can guard your code against.
Offline Jannick

Senior Newbie




Java games rock!


« Reply #21 - Posted 2004-02-27 08:34:53 »

Quote


I'm saying that I don't see the point in enabling non-blocking mode on a socket when using channel selectors.  Why would you?

-Chris


Without non-blocking your write calls would block until the data had been written. the selector giving a writable key doesnt have to mean there is space in the write buffer to write all your data.

Quote
Unless otherwise specified, a write operation will return only after writing all of the r requested bytes. Some types of channels, depending upon their state, may write only some of the bytes or possibly none at all. A socket channel in non-blocking mode, for example, cannot write any more bytes than are free in the socket's output buffer.


That really breaks the concept of multiplexing io where you just want to process what can be processed immidiatly. Im not sure on the behavior of socketchannel.read in blocking mode (and the api doc isnt specific on that) but I would assume it block until buffer is filled.
Offline cknoll

Junior Member




Flame On!


« Reply #22 - Posted 2004-02-27 13:07:49 »

Ok, looking back on my connection code I wrote before with NIO, I also use non-blocking sockets.  so.....nevermind. Smiley

I would like to add, however, that the way I have my server set up, I have one thread waiting for connections and when connections are accepted the new channel is put on a queue that the 'Client input' thread will read from when it is woken up, and I was not seeing the behavior you described.  The server started the 'Accept Listener' thread, and the client processor thread, and the client processor thread would select on a channel with zero channels registered with it, and when a new client connects, the accepter thread puts the channel on the client processor thread's queue, and then calls wakeup() on the selector...this causes the client listener thead to wakeup (zero channels will be in the select list, however), check the queue for new connections, register new connections with the selector, and then process the ready channels (still zero!).  Then, the loop goes back again and the select will block until there is client activity (or another client connects causing the wakeup() to be called).  I've never ran across the behavior you described, maybe I should post some code to compare notes?

-Chris

Offline Jannick

Senior Newbie




Java games rock!


« Reply #23 - Posted 2004-02-27 14:37:05 »

In my code I have a similar setup. I have my ConnectionListener object that also waits for new connections and then give them to the "client processer"/ConnectionWorker object, through the same queue&wakeup mechanism you have describes. That wakeup doesnt cause any problems.

The problem seems to be when wakeup is called and the thread then returns to .select() without having performed some operations that modify its internal state. Also note that it seems that the behavior is almost impossibel to create (ive done it a few times) with conections comming from same machine as the one hosting the server. I'll be using my sparetime the next days to make an application for a friend but after that Ill get coded some extra test classes so that other can try to reproduce the issue (with the intention of making a bug report)
Offline cknoll

Junior Member




Flame On!


« Reply #24 - Posted 2004-02-29 22:48:51 »

Ok, then if modifying the internal state of the selector is the issue, shouldn't you register the channel that was put on the queue after you call wakup()?  However, what i'm thinking now is that if you have 2 connectinos come in at the same time, you could potentially call wakeup() twice (2 new clients) before the selector thread wakes up and processes the connections in the queue...and when it does wake up, it will add two connections to the selector, and then go back to select() but since the second wakeup was issued during the second connect, that means that the selector will wakeup again, but this time there are no connections in the queue to add to the selector to modify the internal state.  (Is this an accurate description of what could happen?)  In this case, i'm not really sure what you should do except maybe as a work around you have a flag that you set that marks if wakeup was already called, and in the selector thread, you clear the flag....hmmm Well if it's fixed in 1.5 even better heh

-Chris
Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

xsi3rr4x (19 views)
2014-04-15 18:08:23

BurntPizza (15 views)
2014-04-15 03:46:01

UprightPath (28 views)
2014-04-14 17:39:50

UprightPath (13 views)
2014-04-14 17:35:47

Porlus (29 views)
2014-04-14 15:48:38

tom_mai78101 (54 views)
2014-04-10 04:04:31

BurntPizza (111 views)
2014-04-08 23:06:04

tom_mai78101 (212 views)
2014-04-05 13:34:39

trollwarrior1 (181 views)
2014-04-04 12:06:45

CJLetsGame (187 views)
2014-04-01 02:16:10
List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:05:20
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!