Commit Graph

598 Commits

Author SHA1 Message Date
Norman Maurer
4f6c21fa97
Fix Http2Headers.method(...) javadocs ()
Motivation:

The javadocs of Http2Headers.method(...) are incorrect, we should fix these.

Modifications:

Correct javadocs

Result:

Fixes https://github.com/netty/netty/issues/8068.
2019-10-29 19:51:28 +01:00
Norman Maurer
bf07592668 Fix typo in test which did introduce a failing test after ffc3b2da72 2019-10-28 09:26:29 +01:00
Julien Hoarau
ffc3b2da72 Validate pseudo and conditional HTTP/2 headers ()
Motivation:

Netty HTTP/2 implementation is not 100% compliant to the spec. This
commit improves the compliance regarding headers validation,
in particular pseudo-headers and connection ones.

According to the spec:
   All HTTP/2 requests MUST include exactly one valid value for the
   ":method", ":scheme", and ":path" pseudo-header fields, unless it is
   a CONNECT request (Section 8.3).  An HTTP request that omits
   mandatory pseudo-header fields is malformed (Section 8.1.2.6).

Modifications:

- Introduce Http2HeadersValidator class capable of validating HTTP/2
headers
- Invoke validation from DefaultHttp2ConnectionDecoder#onHeadersRead
- Modify tests to use valid headers when required
- Modify HttpConversionUtil#toHttp2Headers to not add :scheme and
:path header on CONNECT method in order to conform to the spec

Result:

- Initial requests without :method, :path, :scheme will fail
- Initial requests with multiple values for :method, :path, :scheme
will fail
- Initial requests with an empty :path fail
- Requests with connection-specific header field will fail
- Requests with TE header different than "trailers" will fail
-
- Fixes 8.1.2.2 tests from h2spec 
- Fixes 8.1.2.3 tests from h2spec 
2019-10-27 16:13:01 +01:00
root
844b82b986 [maven-release-plugin] prepare for next development iteration 2019-10-24 12:57:00 +00:00
root
d066f163d7 [maven-release-plugin] prepare release netty-4.1.43.Final 2019-10-24 12:56:30 +00:00
Carl Mastrangelo
e8e7a206b3 Use fast HPACK comparisons when not checking sensitive headers ()
Motivation:
Constant time comparison functions are used to compare HTTP/2 header
values, even if they are not sensitive.

Modification:
After checking for sensitivity, use fast comparison.

Result: Faster HPACK table reads/writes
2019-10-23 23:37:57 -07:00
Norman Maurer
9bf10f2dc5
Correctly propagate failures while update the flow-controller to the … ()
Motivation:

We may fail to update the flow-controller and in this case need to notify the stream channel and close it.

Modifications:

Attach a future to the write of the update frame and in case of a failure propagate it to the channel and close it

Result:

Fixes https://github.com/netty/netty/issues/9663
2019-10-22 05:40:14 -07:00
Norman Maurer
95230e01da
Try to reduce GC produced while writing headers ()
Motivation:

bbc34d0eda introduced correct handling of "in process" setup of streams but there is some room for improvements. Often the writeHeaders(...) is completed directly which means there is not need to create the extra listener object.

Modifications:

- Only create the listener if we really need too.

Result:

Less GC
2019-10-17 10:12:20 -07:00
Matthew Miller
bbc34d0eda HTTP/2: Prevent memory leak when trying to create new streams on a connection that received a GOAWAY. ()
Motivation:

In https://github.com/netty/netty/issues/8692, `Http2FrameCodec` was
updated to keep track of all "being initialized" streams, allocating
memory before initialization begins, and releasing memory after
initialization completes successfully.

In some instances where stream initialization fails (e.g. because this
connection has received a GOAWAY frame), this memory is never released.

Modifications:

This change updates the `Http2FrameCodec` to use a separate promise
for monitoring the success of sending HTTP2 headers. When sending of
headers fails, we now make sure to release memory allocated for stream
initialization.

Result:

After this change, failures in writing HTTP2 Headers (e.g. because this
connection has received a GOAWAY frame) will no longer leak memory.
2019-10-16 19:32:45 -07:00
康智冬
bd8cea644a Fix typos in javadocs ()
Motivation:

We should have correct docs without typos

Modification:

Fix typos and spelling

Result:

More correct docs
2019-10-09 17:12:52 +04:00
Norman Maurer
271d8de9ec
Ensure we finish setup mock before we use it in Http2ConnectionRoundtripTest.headersWriteForPeerStreamWhichWasResetShouldNotGoAway ()
Motivation:

We did dispatch the client code before we did finish setup the mock and so may end up with org.mockito.exceptions.misusing.UnfinishedStubbingException if the connect happens quickly enough.

See https://ci.netty.io/job/netty-centos6-java8-prb/1637/testReport/junit/io.netty.handler.codec.http2/Http2ConnectionRoundtripTest/headersWriteForPeerStreamWhichWasResetShouldNotGoAway/

Modifications:

First finish setup the mock and the dispatch.

Result:

Fix flacky test
2019-10-09 09:10:38 +04:00
Carl Mastrangelo
27397e87b2 Remember to return writability events to flow controller in HTTP2 Multiplexer ()
Motivation:

Http2MultiplexCodec extends Http2FrameCodec extends Http2ConnectionHandler.  It appears  Http2MultiplexCodec overrode the channelWritabilityChanged method, which prevented the flow controller from becoming active.  In the case the parent channel becomes unwritable, and then later becomes writable, it needs to indicate that the child channels can still write data.   This is slightly confusing, because the child channels may still themselves be unwritable, but should still drain their data to the parent channel.

Modification:

Still propagate writability changes to the HTTP/2 flow controller

Result:

Fixes https://github.com/netty/netty/issues/9636
2019-10-08 18:40:23 +04:00
Pete Woods
45be693889 Add io.netty.handler.codec.http2.Http2ConnectionHandler for runtime GraalVM compilation ()
Motivation:

Native image compilation is failing without extra flags:

```
Warning: Aborting stand-alone image build. No instances of io.netty.buffer.UnpooledHeapByteBuf are allowed in the image heap as this class should be initialized at image runtime. Object has been initialized by the io.netty.handler.codec.http2.Http2ConnectionHandler class initializer with a trace: 
 	at io.netty.buffer.Unpooled.wrappedBuffer(Unpooled.java:157)
	at io.netty.handler.codec.http2.Http2ConnectionHandler.<clinit>(Http2ConnectionHandler.java:74)
.  To fix the issue mark io.netty.buffer.UnpooledHeapByteBuf for build-time initialization with --initialize-at-build-time=io.netty.buffer.UnpooledHeapByteBuf or use the the information from the trace to find the culprit and --initialize-at-run-time=<culprit> to prevent its instantiation.

Detailed message:
Trace: 	object io.netty.buffer.ReadOnlyByteBuf
	object io.netty.buffer.UnreleasableByteBuf
	method io.netty.handler.codec.http2.Http2ConnectionHandler.access$500()
Call path from entry point to io.netty.handler.codec.http2.Http2ConnectionHandler.access$500(): 
	at io.netty.handler.codec.http2.Http2ConnectionHandler.access$500(Http2ConnectionHandler.java:66)
	at io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:299)
	at io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239)
	at io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:505)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:444)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:283)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)
	at io.netty.channel.AbstractChannelHandlerContext.access$600(AbstractChannelHandlerContext.java:56)
	at io.netty.channel.AbstractChannelHandlerContext$7.run(AbstractChannelHandlerContext.java:365)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:144)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:89)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:143)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:186)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)
```

Modification:

Add `io.netty.handler.codec.http2.Http2ConnectionHandler` for runtime compilation, as the buffer library's `io.netty.buffer.UnpooledHeapByteBuf` is also marked for runtime.

Result:

Native image compilation works again.
2019-10-07 09:03:07 +02:00
Nikolay Fedorovskikh
1fb5ff15a8 Fix possible NPE in DefaultHttp2UnknownFrame#equals ()
Motivation:
`DefaultHttp2UnknownFrame#equals` may produce NPE due to
incorrect comparison of `stream` field.

Modification:
- Fix the `stream` field compare.
- Cleanup usage of class fields: use direct access instead of getters
(because the class is final).

Result:
No NPE in `equals` method.
2019-10-07 09:00:59 +02:00
Nikolay Fedorovskikh
4dc1eccf60 Make some inner classes static ()
Motivation:
Classes `AbstractHttp2StreamChannel.Http2StreamChannelConfig`
and `DnsNameResolver.AddressedEnvelopeAdapter` may be static:
it doesn't reference its enclosing instance.

Modification:
Add `static` modifier.

Result:
Prevents a possible memory leak and uses less memory per class instance.
2019-10-07 08:14:02 +02:00
Norman Maurer
622cc232f0
Use configured ByteBufAllocator in InboundHttp2ToHttpAdapter ()
Motivation:

At the moment we use Unpooled.buffer(...) in InboundHttp2ToHttpAdapter when we need to do a copy of the message. We should better use the configured ByteBufAllocator for the Channel

Modifications:

Change internal interface to also take the ByteBufAllocator as argument and use it when we need to allocate a ByteBuf.

Result:

Use the "correct" ByteBufAllocator in InboundHttp2ToHttpAdapter in all cases
2019-09-26 22:11:41 +02:00
Norman Maurer
1f4b9e36ea
We should only disable releasing of the message once writeData(...) was called successfully ()
Motivation:

At the moment we set release to false before we call writeData(...). This could let to the sitatuation that we will miss to release the message if writeData(...) throws. We should set release to false after we called writeData(...) to ensure the ownership of the buffer is correctly transferred.

Modifications:

- Set release to false after writeData(...) was successfully called only

Result:

No possibility for a buffer leak
2019-09-26 21:59:57 +02:00
Norman Maurer
299a682d3f
Correctly take Http2FrameCodecBuilder.isValidateHeaders() into account when creating a Http2FrameCodec from an existing Http2FrameWriter. ()
Motivation:

We did miss to take Http2FrameCodecBuilder.isValidateHeaders() into account when a Http2FrameWriter was set on the builder and always assumed validation should be enabled.

Modifications:

Remove hardcode value and use configured value

Result:

Http2FrameCodecBuilder.isValidateHeaders() is respected in all cases
2019-09-26 21:57:05 +02:00
root
92941cdcac [maven-release-plugin] prepare for next development iteration 2019-09-25 06:15:31 +00:00
root
bd907c3b3a [maven-release-plugin] prepare release netty-4.1.42.Final 2019-09-25 06:14:31 +00:00
Pete Woods
0a2d85f1d3 Fix GraalVM native image build error ()
Motivation:

Error: Class that is marked for delaying initialization to run time got initialized during image building: io.netty.handler.codec.http2.Http2CodecUtil. Try marking this class for build-time initialization with --initialize-at-build-time=io.netty.handler.codec.http2.Http2CodecUtil
Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1
Modification:

After debugging, it seems the culprit is io.netty.handler.codec.http2.Http2ClientUpgradeCodec, which also needs runtime initialisation.

Result:

Fixes #micronaut-projects/micronaut-grpc#8
2019-09-23 14:42:42 +02:00
root
01d805bb76 [maven-release-plugin] prepare for next development iteration 2019-09-12 16:09:55 +00:00
root
7cf69022d4 [maven-release-plugin] prepare release netty-4.1.41.Final 2019-09-12 16:09:00 +00:00
root
aef47bec7f [maven-release-plugin] prepare for next development iteration 2019-09-12 05:38:11 +00:00
root
267e5da481 [maven-release-plugin] prepare release netty-4.1.40.Final 2019-09-12 05:37:30 +00:00
Bryce Anderson
a89cde9475 Support cancellation in the Http2StreamChannelBootstrap ()
Motivation:

Right now you can cancel the Future returned by
`Http2StreamChannelBootstrap.open()` and that will race with the
registration of the stream channel with the event loop, potentially
culminating in an `IllegalStateException` and potential resource leak.

Modification:

Ensure that the returned promise is uncancellable.

Result:

Should no longer see `IllegalStateException`s.
2019-08-27 20:42:05 +02:00
nizarm
14e856ac72 Correctly handle client side http2 upgrades when Http2FrameCodec …(9495) ()
Motivation:

In the release (4.1.37) we introduced Http2MultiplexHandler as a
replacement of Http2MultiplexCodec. This did split the frame parsing from
the multiplexing to allow a more flexible way to handle frames and to make
the code cleaner. Unfortunally we did miss to special handle this in
Http2ClientUpgradeCodec and so did not correctly add Http2MultiplexHandler
to the pipeline before calling Http2FrameCodec.onHttpClientUpgrade(...).
This did lead to the situation that we did not correctly receive the event
on the Http2MultiplexHandler and so did not correctly created the
Http2StreamChannel for the upgrade stream. Because of this we ended up
with an NPE if a frame was dispatched to the upgrade stream later on.

Modifications:

- Correctly add Http2MultiplexHandler to the pipeline before calling Http2FrameCodec.onHttpClientUpgrade(...)

Result:

Fixes .
2019-08-23 18:51:57 +02:00
Idel Pivnitskiy
9fa974f6a5 Update links to the latest HTTP/2 specifications ()
Motivation:

Some of the links in javadoc point to the obsolete drafts of HTTP/2
specifications. We should point them to the latest RFC 7540 or 7541.

Modifications:

Update links from `draft-ietf-httpbis-*` to the `rfc7540` and `rfc7541`.

Result:

Correct links in javadoc.
2019-08-22 13:59:08 +02:00
Norman Maurer
bcad76e2db
HTTP2: Update local flow-controller on Channel.read() if needed ()
Motivation:

We should better update the flow-controller on Channel.read() to reduce overhead and memory overhead.

See https://github.com/netty/netty/pull/9390#issuecomment-513008269

Modifications:

Move updateLocalWindowIfNeeded() to doBeginRead()

Result:

Reduce memory overhead
2019-08-16 09:27:47 +02:00
Norman Maurer
d4038d0937
Http2EmptyDataFrameConnectionDecoder.frameListener() should return unwrapped Http2FrameListener ()
Motivation:

As we decorate the Http2FrameListener under the covers we should ensure the user can still access the original Http2FrameListener.

Modifications:

- Unwrap the Http2FrameListener in frameListener()
- Add unit test

Result:

Less suprises for users.
2019-08-16 08:16:15 +02:00
Norman Maurer
1cce3b1ac9
Fix ByteBuf leak in Http2ControlFrameLimitEncoderTest ()
Motivation:

We recently introduced Http2ControlFrameLimitEncoderTest which did not correctly notify the goAway promises and so leaked buffers.

Modifications:

Correctly notify all promises and so release the debug data.

Result:

Fixes leak in HTTP2 test
2019-08-14 13:28:16 +02:00
root
d45a4ce01b [maven-release-plugin] prepare for next development iteration 2019-08-13 17:16:42 +00:00
root
88c2a4cab5 [maven-release-plugin] prepare release netty-4.1.39.Final 2019-08-13 17:15:20 +00:00
Norman Maurer
7003dbdc08
HTTP2: Guard against empty DATA frames (without end_of_stream flag) set ()
Motivation:

It is possible for a remote peer to flood the server / client with empty DATA frames (without end_of_stream flag) set and so cause high CPU usage without the possibility to ever hit a limit. We need to guard against this.

See CVE-2019-9518

Modifications:

- Add a new config option to AbstractHttp2ConnectionBuilder and sub-classes which allows to set the max number of consecutive empty DATA frames (without end_of_stream flag). After this limit is hit we will close the connection. A limit of 10 is used by default.
- Add unit tests

Result:

Guards against CVE-2019-9518
2019-08-13 19:07:10 +02:00
Norman Maurer
cecb46a3dd
HTTP2: Add protection against remote control frames that are triggered by a remote peer ()
Motivation:

Due how http2 spec is defined it is possible by a remote peer to flood us with frames that will trigger control frames as response, the problem here is that the remote peer can also just stop reading these (while still produce more of these) and so may drive us to the pointer where we either run out of memory or burn all CPU. To protect against this we need to implement some kind of limit that will tear down connections that cause the above mentioned situation.

See CVE-2019-9512 / CVE-2019-9514 / CVE-2019-9515

Modifications:

- Add Http2ControlFrameLimitEncoder which limits the number of queued control frames that were caused because of the remote peer.
- Allow to insert ths Http2ControlFrameLimitEncoder by setting AbstractHttp2ConnectionBuilder.encoderEnforceMaxQueuedControlFrames(...) to a number higher then 0. The default is 10000 which provides some protection by default but will hopefully not cause too many false-positives.
- Add unit tests

Result:

Protect against DDOS due control frames. Fixes CVE-2019-9512 / CVE-2019-9514 / CVE-2019-9515 .
2019-08-13 19:02:20 +02:00
Norman Maurer
6862ab76c0
Delay Http2ConnectionPrefaceAndSettingsFrameWrittenEvent by one EventLoop tick when using the Http2FrameCodec ()
Motivation:

We should delay the firing of the Http2ConnectionPrefaceAndSettingsFrameWrittenEvent by one EventLoop tick when using the Http2FrameCodec to ensure all handlers are added to the pipeline before the event is passed through it.

This is needed to workaround a race that could happen when the preface is send in handlerAdded(...) but a later handler wants to act on the event.

Modifications:

Offload firing of the event to the EventExecutor.

Result:

Fixes https://github.com/netty/netty/issues/9432.
2019-08-13 10:50:18 +02:00
root
718b7626e6 [maven-release-plugin] prepare for next development iteration 2019-07-24 09:05:57 +00:00
root
465c900c04 [maven-release-plugin] prepare release netty-4.1.38.Final 2019-07-24 09:05:23 +00:00
Norman Maurer
513e9f2893
HTTP/2: Ensure newStream() is called only once per connection upgrade and the correct handler is used ()
Motivation:

306299323c introduced some code change to move the responsibility of creating the stream for the upgrade to Http2FrameCodec. Unfortunaly this lead to the situation of having newStream().setStreamAndProperty(...) be called twice. Because of this we only ever saw the channelActive(...) on Http2StreamChannel but no other events as the mapping was replaced on the second newStream().setStreamAndProperty(...) call.

Beside this we also did not use the correct handler for the upgrade stream in some cases

Modifications:

- Just remove the Http2FrameCodec.onHttpClientUpgrade() method and so let the base class handle all of it. The stream is created correctly as part of the ConnectionListener implementation of Http2FrameCodec already.
- Consolidate logic of creating stream channels
- Adjust unit test to capture the bug

Result:

Fixes https://github.com/netty/netty/issues/9395
2019-07-23 21:05:39 +02:00
Norman Maurer
60cf18cf20
HTTP/2 multiplex: Correctly process buffered inbound data even if autoRead is false ()
Motivation:

When using the HTTP/2 multiplex implementation we need to ensure we correctly drain the buffered inbound data even if the RecvByteBufallocator.Handle tells us to stop reading in between.

Modifications:

Correctly loop through the buffered inbound data until the user does stop to request from it.

Result:

Fixes https://github.com/netty/netty/issues/9387.

Co-authored-by: Bryce Anderson <banderson@twitter.com>
2019-07-21 20:58:23 +02:00
Norman Maurer
04afa3a07e
Reuse Http2FrameStreamEvent instances to reduce GC pressure ()
Motivation:

We can easily reuse the Http2FrameStreamEvent instances and so reduce GC pressure as there may be multiple events per streams over the life-time.

Modifications:

Reuse instances

Result:

Less allocations
2019-07-21 20:35:35 +02:00
Norman Maurer
84cf8f14e9
Cache the ChannelHandlerContext used in Http2StreamChannelBootstrap ()
Motivation:

At the moment we lookup the ChannelHandlerContext used in Http2StreamChannelBootstrap each time the open(...) method is invoked. This is not needed and we can just cache it for later usage.

Modifications:

Cache ChannelHandlerContext in volatile field.

Result:

Speed up open(...) method implementation when called multiple times
2019-07-18 10:20:34 +02:00
Bryce Anderson
dd1785ba66 Fix an NPE in AbstractHttp2StreamChannel ()
Motivation:

If a read triggers a AbstractHttp2StreamChannel to close we can
get an NPE in the read loop.

Modifications:

Make sure that the inboundBuffer isn't null before attempting to
continue the loop.

Result:

No NPE.
Fixes 
2019-07-17 20:12:19 +02:00
Norman Maurer
306299323c
Move responsibility for creating upgrade stream to Http2FrameCodec ()
Motivation:

The Http2FrameCodec should be responsible to create the upgrade stream.

Modifications:

Move code to create stream to Http2FrameCodec

Result:

More correct responsibility
2019-07-16 13:24:45 +02:00
Norman Maurer
4f172c13bb
Add deprecation to Http2StreamChannelBootstrap.open0(...) as it was marked as public by mistake ()
Motivation:

Mark Http2StreamChannelBootstrap.open0(...) as deprecated as the user should not use it. It was marked as public by mistake.

Modifications:

Add deprecation warning.

Result:

User will be aware the method should not be used directly.
2019-07-16 13:08:09 +02:00
Norman Maurer
906fc02b3f
Allow to disable automatically sending PING acks. ()
Motivation:

There are situations where the user may want to be more flexible when to send the PING acks for various reasons or be able to attach a listener to the future that is used for the ping ack. To be able to do so we should allow to manage the acks manually.

Modifications:

- Add constructor to DefaultHttp2ConnectionDecoder that allows to disable the automatically sending of ping acks (default is to send automatically to not break users)
- Add methods to AbstractHttp2ConnectionHandlerBuilder (and sub-classes) to either enable ot disable auto acks for pings
- Make DefaultHttp2PingFrame constructor public that allows to write acks.
- Add unit test

Result:

More flexible way of handling acks.
2019-07-12 18:15:06 +02:00
jingene
c0f9364870 Change the netty.io homepage scheme(http -> https) ()
Motivation:

Netty homepage(netty.io) serves both "http" and "https".
It's recommended to use https than http.
Modification:

I changed from "http://netty.io" to "https://netty.io"
Result:

No effects.
2019-07-09 21:09:42 +02:00
Norman Maurer
bded2a1c75
HTTP2: Always apply the graceful shutdown timeout if configured ()
Motivation:

Http2ConnectionHandler (and sub-classes) allow to configure a graceful shutdown timeout but only apply it if there is at least one active stream. We should always apply the timeout. This is also true when we try to send a GO_AWAY and close the connection because of an connection error.

Modifications:

- Always apply the timeout if one is configured
- Add unit test

Result:

Always respect gracefulShutdownTimeoutMillis
2019-07-09 21:05:34 +02:00
Norman Maurer
4312e11316
DecoratingHttp2ConnectionEncoder.consumeRemoteSettings must not throw if delegate is instance of Http2SettingsReceivedConsumer ()
Motivation:

b3dba317d7 introduced the concept of Http2SettingsReceivedConsumer but did not correctly inplement DecoratingHttp2ConnectionEncoder.consumeRemoteSettings(...).

Modifications:

- Add missing `else` around the throws
- Add unit tests

Result:

Correctly implement DecoratingHttp2ConnectionEncoder.consumeRemoteSettings(...)
2019-07-09 14:39:32 +02:00
Nick Hill
91d6e0ea8f Simplify HpackHuffmanDecoder table decode logic ()
Motivation

The nice change made by @carl-mastrangelo in  for lookup-table
based HPACK Huffman decoding can be simplified a little to remove the
separate flags field and eliminate some intermediate operations.

Modification

Simplify HpackHuffmanDecoder::decode logic including de-dup of the
per-nibble part.

Result

Less code, possibly better performance though not noticeable in a quick
benchmark.
2019-07-08 12:04:20 +02:00