From 791a6a25f6670a23d390bfdeb786f70588d622a5 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Thu, 31 Jan 2019 00:25:02 +0100 Subject: [PATCH] Add custom EXI buffer management 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 --- .../nettyutil/handler/NetconfEXICodec.java | 14 ++---- .../handler/NetconfEXIToMessageDecoder.java | 5 +- .../handler/ThreadLocalSAXDecoder.java | 47 +++++++++++++++++++ .../handler/ThreadLocalSAXFactory.java | 26 ++++++++++ 4 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXDecoder.java create mode 100644 netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXFactory.java diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXICodec.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXICodec.java index 7c74416e62..415afe3d0a 100644 --- a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXICodec.java +++ b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXICodec.java @@ -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(); } } diff --git a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXIToMessageDecoder.java b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXIToMessageDecoder.java index 4ea4202adc..0f85d18c16 100644 --- a/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXIToMessageDecoder.java +++ b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/NetconfEXIToMessageDecoder.java @@ -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 index 0000000000..bc97b62961 --- /dev/null +++ b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXDecoder.java @@ -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 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 index 0000000000..821dce886e --- /dev/null +++ b/netconf/netconf-netty-util/src/main/java/org/opendaylight/netconf/nettyutil/handler/ThreadLocalSAXFactory.java @@ -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); + } +} -- 2.36.6