|
27 | 27 | import static org.htmlunit.BrowserVersionFeatures.WINDOW_EXECUTE_EVENTS; |
28 | 28 |
|
29 | 29 | import java.io.BufferedInputStream; |
30 | | -import java.io.Closeable; |
31 | 30 | import java.io.File; |
32 | 31 | import java.io.IOException; |
33 | 32 | import java.io.InputStream; |
|
55 | 54 | import java.util.Objects; |
56 | 55 | import java.util.Optional; |
57 | 56 | import java.util.Set; |
| 57 | +import java.util.concurrent.ConcurrentLinkedDeque; |
58 | 58 | import java.util.concurrent.Executor; |
59 | 59 | import java.util.concurrent.ExecutorService; |
60 | 60 | import java.util.concurrent.Executors; |
|
151 | 151 | * @author Anton Demydenko |
152 | 152 | * @author Sergio Moreno |
153 | 153 | * @author Lai Quang Duong |
| 154 | + * @author René Schwietzke |
154 | 155 | */ |
155 | 156 | public class WebClient implements Serializable, AutoCloseable { |
156 | 157 |
|
@@ -206,9 +207,8 @@ public class WebClient implements Serializable, AutoCloseable { |
206 | 207 | private OnbeforeunloadHandler onbeforeunloadHandler_; |
207 | 208 | private Cache cache_ = new Cache(); |
208 | 209 |
|
209 | | - // mini pool to save resource when parsing css |
210 | | - private transient PooledCSS3Parser firstPooledCSS3Parser_ = new PooledCSS3Parser(); |
211 | | - private transient PooledCSS3Parser secondPooledCSS3Parser_ = new PooledCSS3Parser(); |
| 210 | + // mini pool to save resource when parsing CSS |
| 211 | + private transient CSS3ParserPool css3ParserPool = new CSS3ParserPool(); |
212 | 212 |
|
213 | 213 | /** target "_blank". */ |
214 | 214 | public static final String TARGET_BLANK = "_blank"; |
@@ -2954,46 +2954,120 @@ public XHtmlPage loadXHtmlCodeIntoCurrentWindow(final String xhtmlCode) throws I |
2954 | 2954 | /** |
2955 | 2955 | * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> |
2956 | 2956 | * |
2957 | | - * @return CSS3Parser from pool |
| 2957 | + * @return a CSS3Parser that will return to an internal pool for reuse if closed using the |
| 2958 | + * try-with-resource concept |
2958 | 2959 | */ |
2959 | | - public PooledCSS3Parser getCSS3ParserFromPool() { |
2960 | | - if (!firstPooledCSS3Parser_.inUse_) { |
2961 | | - if (firstPooledCSS3Parser_.open()) { |
2962 | | - return firstPooledCSS3Parser_; |
2963 | | - } |
2964 | | - } |
2965 | | - if (!secondPooledCSS3Parser_.inUse_) { |
2966 | | - if (secondPooledCSS3Parser_.open()) { |
2967 | | - return secondPooledCSS3Parser_; |
2968 | | - } |
2969 | | - } |
2970 | | - |
2971 | | - return new PooledCSS3Parser(); |
| 2960 | + public PooledCSS3Parser getCSS3Parser() { |
| 2961 | + return this.css3ParserPool.get(); |
2972 | 2962 | } |
2973 | 2963 |
|
2974 | | - public static class PooledCSS3Parser implements Closeable { |
2975 | | - private final CSS3Parser css3Parser_; |
2976 | | - private boolean inUse_; |
2977 | | - |
2978 | | - public PooledCSS3Parser() { |
2979 | | - css3Parser_ = new CSS3Parser(); |
2980 | | - } |
2981 | | - |
2982 | | - public CSS3Parser css3Parser() { |
2983 | | - return css3Parser_; |
2984 | | - } |
2985 | | - |
2986 | | - public synchronized boolean open() { |
2987 | | - if (inUse_) { |
2988 | | - return false; |
2989 | | - } |
2990 | | - inUse_ = true; |
2991 | | - return true; |
2992 | | - } |
2993 | | - |
| 2964 | + /** |
| 2965 | + * Our pool of CSS3Parsers. If you need a parser, get it from here and use the AutoCloseable |
| 2966 | + * functionality with a try-with-resource block. If you don't want to do that at all, continue |
| 2967 | + * to build CSS3Parsers the old fashioned way. |
| 2968 | + * |
| 2969 | + * Fetching a parser is thread safe. This API is built to minimize synchronization overhead, |
| 2970 | + * hence it is possible to miss a returned parser from another thread under heavy pressure, |
| 2971 | + * but because that is unlikely, we keep it simple and efficient. Caches are not supposed |
| 2972 | + * to give cutting-edge guarantees. |
| 2973 | + * |
| 2974 | + * This concept avoids a resource leak when someone does not close the fetched |
| 2975 | + * parser because the pool does not know anything about the parser unless |
| 2976 | + * it returns. We are not running a checkout-checkin concept. |
| 2977 | + * |
| 2978 | + * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> |
| 2979 | + */ |
| 2980 | + public static class CSS3ParserPool { |
| 2981 | + /* |
| 2982 | + * Our pool. We only hold data when it is available. In addition, synchronization against |
| 2983 | + * this deque is cheap. |
| 2984 | + */ |
| 2985 | + private ConcurrentLinkedDeque<PooledCSS3Parser> parsers = new ConcurrentLinkedDeque<>(); |
| 2986 | + |
| 2987 | + /** |
| 2988 | + * Fetch a new or recycled CSS3parser. Make sure you use the try-with-resource concept |
| 2989 | + * to automatically return it after use because a parser creation is expensive. |
| 2990 | + * We won't get a leak, if you don't do so, but that will remove the advantage. |
| 2991 | + * |
| 2992 | + * @return a parser |
| 2993 | + */ |
| 2994 | + public PooledCSS3Parser get() { |
| 2995 | + // see if we have one, LIFO |
| 2996 | + final PooledCSS3Parser parser = parsers.pollLast(); |
| 2997 | + |
| 2998 | + // if we don't have one, get us one |
| 2999 | + return parser != null ? parser.markInUse(this) : new PooledCSS3Parser(this); |
| 3000 | + } |
| 3001 | + |
| 3002 | + /** |
| 3003 | + * Return a parser. Normally you don't have to use that method explicitly. |
| 3004 | + * Prefer to user the AutoCloseable interface of the PooledParser by |
| 3005 | + * using a try-with-resource statement. |
| 3006 | + * |
| 3007 | + * @param parser the parser to recycle |
| 3008 | + */ |
| 3009 | + protected void recycle(final PooledCSS3Parser parser) { |
| 3010 | + parsers.addLast(parser); |
| 3011 | + } |
| 3012 | + } |
| 3013 | + |
| 3014 | + /** |
| 3015 | + * This is a poolable CSS3Parser which can be reused automatically when closed. |
| 3016 | + * A regular CSS3Parser is not thread-safe, hence also our pooled parser |
| 3017 | + * is not thread-safe. |
| 3018 | + * |
| 3019 | + * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br> |
| 3020 | + */ |
| 3021 | + public static class PooledCSS3Parser extends CSS3Parser implements AutoCloseable { |
| 3022 | + /** |
| 3023 | + * The pool we want to return us to |
| 3024 | + */ |
| 3025 | + private CSS3ParserPool pool; |
| 3026 | + |
| 3027 | + /** |
| 3028 | + * Create a new poolable parser |
| 3029 | + * |
| 3030 | + * @param pool the pool the parser should return to when it is closed |
| 3031 | + */ |
| 3032 | + protected PooledCSS3Parser(final CSS3ParserPool pool) { |
| 3033 | + super(); |
| 3034 | + this.pool = pool; |
| 3035 | + } |
| 3036 | + |
| 3037 | + /** |
| 3038 | + * Resets the parser's pool state so it can be safely returned again |
| 3039 | + * |
| 3040 | + * @param pool the pool the parser should return to when it is closed |
| 3041 | + */ |
| 3042 | + protected PooledCSS3Parser markInUse(final CSS3ParserPool pool) { |
| 3043 | + // ensure we detect programming mistakes, Java will optimize this |
| 3044 | + // null check away when it never happens |
| 3045 | + if (this.pool == null) { |
| 3046 | + this.pool = pool; |
| 3047 | + } |
| 3048 | + else { |
| 3049 | + throw new IllegalStateException("This PooledParser was not returned to the pool properly"); |
| 3050 | + } |
| 3051 | + |
| 3052 | + return this; |
| 3053 | + } |
| 3054 | + |
| 3055 | + /** |
| 3056 | + * Implements the AutoClosable interface. The return method ensures that |
| 3057 | + * we are notified when we incorrectly close it twice which indicates a |
| 3058 | + * programming flow defect. |
| 3059 | + * |
| 3060 | + * @throws IllegalStateException in case the parser is closed several times |
| 3061 | + */ |
2994 | 3062 | @Override |
2995 | | - public void close() throws IOException { |
2996 | | - inUse_ = false; |
| 3063 | + public void close() { |
| 3064 | + if (this.pool != null) { |
| 3065 | + this.pool.recycle(this); |
| 3066 | + this.pool = null; |
| 3067 | + } |
| 3068 | + else { |
| 3069 | + throw new IllegalStateException("This PooledParser was returned already"); |
| 3070 | + } |
2997 | 3071 | } |
2998 | 3072 | } |
2999 | 3073 | } |
0 commit comments