Add custom EXI buffer management 46/76546/7
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 30 Jan 2019 23:25:02 +0000 (00:25 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 31 Jan 2019 13:12:05 +0000 (14:12 +0100)
Exificient's SAXDecoder by default does not free its internal buffer,
and allocates one for each decoder. With version 1.0.4 we have enough
visibility to perform buffer management.

This adds a thread-local buffer, which is reused during parse operation
and retained if it is under 64kB after the operation completes.

Change-Id: Ie2f7c72b1160d389a07c473fda6739b7eb7212cb
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXICodec.java
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXIToMessageDecoder.java
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXDecoder.java [new file with mode: 0644]
netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXFactory.java [new file with mode: 0644]

index 7c74416e6226059a98ba00c094f9147bbcf26f71..415afe3d0abad056a01a6b6301ec05f972177025 100644 (file)
@@ -5,7 +5,6 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.netconf.nettyutil.handler;
 
 import static java.util.Objects.requireNonNull;
@@ -17,10 +16,8 @@ import org.opendaylight.netconf.nettyutil.handler.exi.EXIParameters;
 import org.opendaylight.netconf.shaded.exificient.core.EXIFactory;
 import org.opendaylight.netconf.shaded.exificient.core.exceptions.EXIException;
 import org.opendaylight.netconf.shaded.exificient.main.api.sax.SAXEncoder;
-import org.opendaylight.netconf.shaded.exificient.main.api.sax.SAXFactory;
 import org.xml.sax.EntityResolver;
 import org.xml.sax.InputSource;
-import org.xml.sax.XMLReader;
 
 public final class NetconfEXICodec {
     /**
@@ -41,24 +38,23 @@ public final class NetconfEXICodec {
                 }
             });
 
-    private final SAXFactory exiFactory;
+    private final ThreadLocalSAXFactory exiFactory;
 
     private NetconfEXICodec(final EXIFactory exiFactory) {
-        this.exiFactory = new SAXFactory(requireNonNull(exiFactory));
+        this.exiFactory = new ThreadLocalSAXFactory(requireNonNull(exiFactory));
     }
 
     public static NetconfEXICodec forParameters(final EXIParameters parameters) {
         return CODECS.getUnchecked(parameters);
     }
 
-    XMLReader getReader() throws EXIException {
-        final XMLReader reader = exiFactory.createEXIReader();
+    ThreadLocalSAXDecoder getReader() throws EXIException {
+        final ThreadLocalSAXDecoder reader = exiFactory.createEXIReader();
         reader.setEntityResolver(ENTITY_RESOLVER);
         return reader;
     }
 
     SAXEncoder getWriter() throws EXIException {
-        final SAXEncoder writer = exiFactory.createEXIWriter();
-        return writer;
+        return exiFactory.createEXIWriter();
     }
 }
index 4ea4202adc6a0f5e50f0bcdda641e339bb61068c..0f85d18c16964be7b72b3d94fcb504f937e1a99d 100644 (file)
@@ -32,7 +32,6 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
 
 public final class NetconfEXIToMessageDecoder extends ByteToMessageDecoder {
 
@@ -54,10 +53,10 @@ public final class NetconfEXIToMessageDecoder extends ByteToMessageDecoder {
      * which means that {@link #decode(ChannelHandlerContext, ByteBuf, List)}
      * cannot be invoked concurrently. Hence we can reuse the reader.
      */
-    private final XMLReader reader;
+    private final ThreadLocalSAXDecoder reader;
     private final DocumentBuilder documentBuilder;
 
-    private NetconfEXIToMessageDecoder(final XMLReader reader) {
+    private NetconfEXIToMessageDecoder(final ThreadLocalSAXDecoder reader) {
         this.reader = requireNonNull(reader);
         this.documentBuilder = UntrustedXML.newDocumentBuilder();
     }
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXDecoder.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXDecoder.java
new file mode 100644 (file)
index 0000000..bc97b62
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2019 Pantheon Technologies, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.nettyutil.handler;
+
+import java.io.IOException;
+import org.opendaylight.netconf.shaded.exificient.core.EXIFactory;
+import org.opendaylight.netconf.shaded.exificient.core.exceptions.EXIException;
+import org.opendaylight.netconf.shaded.exificient.main.api.sax.SAXDecoder;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Utility SAXDecoder, which reuses a thread-local buffer during parse operations.
+ */
+final class ThreadLocalSAXDecoder extends SAXDecoder {
+    // Note these limits are number of chars, i.e. 2 bytes
+    private static final int INITIAL_SIZE = 4096;
+    private static final int CACHE_MAX_SIZE = 32768;
+    private static final ThreadLocal<char[]> CBUFFER_CACHE = ThreadLocal.withInitial(() -> new char[INITIAL_SIZE]);
+
+    ThreadLocalSAXDecoder(final EXIFactory noOptionsFactory) throws EXIException {
+        super(noOptionsFactory, null);
+    }
+
+    @Override
+    public void parse(final InputSource source) throws IOException, SAXException {
+        cbuffer = CBUFFER_CACHE.get();
+        final int startSize = cbuffer.length;
+        try {
+            super.parse(source);
+        } finally {
+            char[] toCache = cbuffer;
+            cbuffer = null;
+            if (toCache.length > CACHE_MAX_SIZE && startSize < CACHE_MAX_SIZE) {
+                // The buffer grew to large for caching, but make sure we enlarge our cached buffer to prevent
+                // some amount of reallocations in future.
+                toCache = new char[CACHE_MAX_SIZE];
+            }
+            CBUFFER_CACHE.set(toCache);
+        }
+    }
+}
diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXFactory.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXFactory.java
new file mode 100644 (file)
index 0000000..821dce8
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2019 Pantheon Technologies, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.netconf.nettyutil.handler;
+
+import org.opendaylight.netconf.shaded.exificient.core.EXIFactory;
+import org.opendaylight.netconf.shaded.exificient.core.exceptions.EXIException;
+import org.opendaylight.netconf.shaded.exificient.main.api.sax.SAXFactory;
+
+/**
+ * A SAXFactory which hands out {@link ThreadLocalSAXDecoder}s.
+ */
+final class ThreadLocalSAXFactory extends SAXFactory {
+    ThreadLocalSAXFactory(final EXIFactory exiFactory) {
+        super(exiFactory);
+    }
+
+    @Override
+    public ThreadLocalSAXDecoder createEXIReader() throws EXIException {
+        return new ThreadLocalSAXDecoder(exiFactory);
+    }
+}