xref: /unit/src/java/nginx/unit/websocket/AsyncChannelGroupUtil.java (revision 1157:7ae152bda303)
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 package nginx.unit.websocket;
18 
19 import java.io.IOException;
20 import java.nio.channels.AsynchronousChannelGroup;
21 import java.security.AccessController;
22 import java.security.PrivilegedAction;
23 import java.util.concurrent.ExecutorService;
24 import java.util.concurrent.SynchronousQueue;
25 import java.util.concurrent.ThreadFactory;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicInteger;
28 
29 import org.apache.tomcat.util.res.StringManager;
30 import org.apache.tomcat.util.threads.ThreadPoolExecutor;
31 
32 /**
33  * This is a utility class that enables multiple {@link WsWebSocketContainer}
34  * instances to share a single {@link AsynchronousChannelGroup} while ensuring
35  * that the group is destroyed when no longer required.
36  */
37 public class AsyncChannelGroupUtil {
38 
39     private static final StringManager sm =
40             StringManager.getManager(AsyncChannelGroupUtil.class);
41 
42     private static AsynchronousChannelGroup group = null;
43     private static int usageCount = 0;
44     private static final Object lock = new Object();
45 
46 
AsyncChannelGroupUtil()47     private AsyncChannelGroupUtil() {
48         // Hide the default constructor
49     }
50 
51 
register()52     public static AsynchronousChannelGroup register() {
53         synchronized (lock) {
54             if (usageCount == 0) {
55                 group = createAsynchronousChannelGroup();
56             }
57             usageCount++;
58             return group;
59         }
60     }
61 
62 
unregister()63     public static void unregister() {
64         synchronized (lock) {
65             usageCount--;
66             if (usageCount == 0) {
67                 group.shutdown();
68                 group = null;
69             }
70         }
71     }
72 
73 
createAsynchronousChannelGroup()74     private static AsynchronousChannelGroup createAsynchronousChannelGroup() {
75         // Need to do this with the right thread context class loader else the
76         // first web app to call this will trigger a leak
77         ClassLoader original = Thread.currentThread().getContextClassLoader();
78 
79         try {
80             Thread.currentThread().setContextClassLoader(
81                     AsyncIOThreadFactory.class.getClassLoader());
82 
83             // These are the same settings as the default
84             // AsynchronousChannelGroup
85             int initialSize = Runtime.getRuntime().availableProcessors();
86             ExecutorService executorService = new ThreadPoolExecutor(
87                     0,
88                     Integer.MAX_VALUE,
89                     Long.MAX_VALUE, TimeUnit.MILLISECONDS,
90                     new SynchronousQueue<Runnable>(),
91                     new AsyncIOThreadFactory());
92 
93             try {
94                 return AsynchronousChannelGroup.withCachedThreadPool(
95                         executorService, initialSize);
96             } catch (IOException e) {
97                 // No good reason for this to happen.
98                 throw new IllegalStateException(sm.getString("asyncChannelGroup.createFail"));
99             }
100         } finally {
101             Thread.currentThread().setContextClassLoader(original);
102         }
103     }
104 
105 
106     private static class AsyncIOThreadFactory implements ThreadFactory {
107 
108         static {
109             // Load NewThreadPrivilegedAction since newThread() will not be able
110             // to if called from an InnocuousThread.
111             // See https://bz.apache.org/bugzilla/show_bug.cgi?id=57490
NewThreadPrivilegedAction.load()112             NewThreadPrivilegedAction.load();
113         }
114 
115 
116         @Override
newThread(final Runnable r)117         public Thread newThread(final Runnable r) {
118             // Create the new Thread within a doPrivileged block to ensure that
119             // the thread inherits the current ProtectionDomain which is
120             // essential to be able to use this with a Java Applet. See
121             // https://bz.apache.org/bugzilla/show_bug.cgi?id=57091
122             return AccessController.doPrivileged(new NewThreadPrivilegedAction(r));
123         }
124 
125         // Non-anonymous class so that AsyncIOThreadFactory can load it
126         // explicitly
127         private static class NewThreadPrivilegedAction implements PrivilegedAction<Thread> {
128 
129             private static AtomicInteger count = new AtomicInteger(0);
130 
131             private final Runnable r;
132 
NewThreadPrivilegedAction(Runnable r)133             public NewThreadPrivilegedAction(Runnable r) {
134                 this.r = r;
135             }
136 
137             @Override
run()138             public Thread run() {
139                 Thread t = new Thread(r);
140                 t.setName("WebSocketClient-AsyncIO-" + count.incrementAndGet());
141                 t.setContextClassLoader(this.getClass().getClassLoader());
142                 t.setDaemon(true);
143                 return t;
144             }
145 
load()146             private static void load() {
147                 // NO-OP. Just provides a hook to enable the class to be loaded
148             }
149         }
150     }
151 }
152