Hello Xuelei, List,

thanks for taking the time to comment:

Am 16.01.2013, 05:04 Uhr, schrieb Xuelei Fan <[email protected]>:

I agree with you that create new threads in SSLSocket implementation is
not good.  The application is the better place to decide what's the
right thread model.

 For the same reason, using Executor in SSLSocket
implementation might not be an option from my understanding.


A small change without Executor would be to have a boolean setter which deactivates the Thread dispatching. The default will use new Threads, if direct mode is enabled it will directly call the listeners in the data thread:

public void setDirectHandshakeListener(boolean enabled)
{
        this.skipListnerBackgroundThread = enabled;
}

private void readRecord(InputRecord r, boolean needAppData)
        ...
        if (handshakeListeners != null) {
                HandshakeCompletedEvent event = new 
HandshakeCompletedEvent(this, sess);
                Runnable r = NotifyHandshakeTask(handshakeListeners.entrySet(), 
event);
                if (skipListnerBackgroundThread == false) {
                        Thread t = new Thread("HandshakeEventThread", r);
                        t.start();
                } else {
                        r.run();
                }
        }

This also would require to transform NotifyHandshakeThread into a runable (or move it to the Event, see below)


But I think setter for different Executor strategies is not more overhead but more flexible. It would allow to use a smarter default strategy and it enables the user to request the sync case by passing a: "class DirectExecutor implements Executor { void execute(Runnable r) { r.run(); }}".


SSLContextImpl
--------------
Executor listenerExecutor = Executors.newCachedExecutor();

SSLSocketFactoryImpl:
--------------------
public void setHandshakeListenerExecutor(Executor newExecutor)
{
        context.listenerExecutor = newExecutor;
}

SSLSocketImpl:
private void readRecord(InputRecord r, boolean needAppData)
        ...
        if (handshakeListeners != null) {
                HandshakeCompletedEvent event = new 
HandshakeCompletedEvent(this, sess);
                Runnable r = NotifyHandshakeTask(handshakeListeners.entrySet(), 
event);
                // if (context.listenerExecutor == null) r.run(); else
                context.listenerExecutur.execute(r);
        }



The HashSet clone should be pretty fast.  A kind of "copy" of listeners
is necessary here, otherwise, need to consider more about the
synchronization between the update (add/remove) and use of the listeners.

Actually that copy constructor was introduced as a IMHO incorrect Fix. Because the copy constructor runs concurrently to the add/removeListener methods and is not concurrency safe (HashMap#putAllForCreate is a foreach!). The fix 7065972 will reduce the race window (which is very small anyway) but it still exists.

So if you fix this I would move copy/entrySetCreation/IteratorCreation out of the hot path.

Something like this is needed. In this version it remebers the HashMap and a array version of it. It seems there is no good "ListernerRegistrationMapSupport" object similiar to java.beans.ChangeListenerMap in the Java RT lib?


// modified by add/removeHandshakeCompletedListener (synchronmized on this only) HashMap<HandshakeCompletedListener, AccessControlContext> handshakeListeners;

// never modified only replaced (replace/dereference with monitor on this so no volatile needed) Map.Enty<HandshakeCompletedListener, AccessControlContect>[] handshakeListenersArray = null;


public synchronized void addHandshakeCompletedListener(HandshakeCompletedListener listener)
{
        if (listener == null)
        {
                throw new IllegalArgumentException("listener is null");
        }

        // implement a copy on write strategy so handshakeListeners is immutable
        if (handshakeListeners == null) {
handshakeListeners = new HashMap<HandshakeCompletedListener, AccessControlContext>(4);
        }
        handshakeListeners.put(listener, AccessController.getContext());
        // create a immutable array for the iterator
handshakeListenersArray = handshakeListeners.entrySet().toArray(new Map.Entry<K, V>[m.size()]);
}
...
        if (handshakeListenersArray != null) {
                HandshakeCompletedEvent event = new 
HandshakeCompletedEvent(this, sess);
                Thread t = new NotifyHandshakeThread(handshakeListenersArray, 
event);
                t.start();
        }
...

        NotifyHandshakeThread(
                Map.Entry<HandshakeCompletedListener,AccessControlContext>[] 
entryArray,
                HandshakeCompletedEvent e) {
                        super("HandshakeCompletedNotify-Thread");
                        targets = entryArray; // is immutable
                        event = e;
        }
...
        public void run() {
                for (int i=0;i<targets.lenght;i++) {
                        HandshakeCompletedListener l = targets[i].getKey();
                        AccessControlContext acc = targets[i].getValue();
...



I'm not sure I understand the suggestion.  Why it is helpful to reduce
objects allocations?  Can you show some examples?

Well, for a case where the same functionality can be achieved with less
garbage produced I would prefer the more economic implementation. So you can remove the NotifyHandshakeThread class completely by adding a Runnable Interface to HandshakeCompletedEvent. But yes, thats only a minor optimisation for reducing the GC load.

BTW: Is there somewhere a Git repository of the JSSE part? Would be faster for me to get it build. If not, I might use a fork of this https://github.com/benmmurphy/ssl_npn to implement my suggestion.

Greetings
Bernd
--
http://bernd.eckenfels.net

Reply via email to