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