You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2023/04/01 20:11:11 UTC
[cxf] branch 3.4.x-fixes updated (7d06b10b8e -> 737a98679f)
This is an automated email from the ASF dual-hosted git repository.
reta pushed a change to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
from 7d06b10b8e CXF-8824: CDI beans produced by @produces methods are generated twice (#1163)
new ffa7238c05 CXF-8826: AsyncHTTPConduit (hc5) - Unexpected EOF during response processing (#1205)
new 737a98679f Recording .gitmergeinfo Changes
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.gitmergeinfo | 6 +
.../AsyncHTTPConduitLargeResponseTest.java} | 173 ++++----------------
.../http/asyncclient/hc5/SharedInputBuffer.java | 39 ++++-
.../hc5/AsyncHTTPConduitLargeResponseTest.java} | 177 ++++-----------------
4 files changed, 108 insertions(+), 287 deletions(-)
copy rt/transports/{http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduitTest.java => http-hc/src/test/java/org/apache/cxf/transport/http/asyncclient/AsyncHTTPConduitLargeResponseTest.java} (50%)
copy rt/transports/{http-netty/netty-client/src/test/java/org/apache/cxf/transport/http/netty/client/NettyHttpConduitTest.java => http-hc5/src/test/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitLargeResponseTest.java} (50%)
[cxf] 01/02: CXF-8826: AsyncHTTPConduit (hc5) - Unexpected EOF during response processing (#1205)
Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit ffa7238c05874ae733875527e7211f5b65f2926a
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Sat Apr 1 15:14:03 2023 -0400
CXF-8826: AsyncHTTPConduit (hc5) - Unexpected EOF during response processing (#1205)
---
.../AsyncHTTPConduitLargeResponseTest.java | 168 ++++++++++++++++++++
.../http/asyncclient/hc5/SharedInputBuffer.java | 39 ++++-
.../hc5/AsyncHTTPConduitLargeResponseTest.java | 172 +++++++++++++++++++++
3 files changed, 376 insertions(+), 3 deletions(-)
diff --git a/rt/transports/http-hc/src/test/java/org/apache/cxf/transport/http/asyncclient/AsyncHTTPConduitLargeResponseTest.java b/rt/transports/http-hc/src/test/java/org/apache/cxf/transport/http/asyncclient/AsyncHTTPConduitLargeResponseTest.java
new file mode 100644
index 0000000000..fbc86363dc
--- /dev/null
+++ b/rt/transports/http-hc/src/test/java/org/apache/cxf/transport/http/asyncclient/AsyncHTTPConduitLargeResponseTest.java
@@ -0,0 +1,168 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.transport.http.asyncclient;
+
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.ws.AsyncHandler;
+import javax.xml.ws.Endpoint;
+import javax.xml.ws.Response;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.continuations.Continuation;
+import org.apache.cxf.continuations.ContinuationProvider;
+import org.apache.cxf.frontend.ClientProxy;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.transport.http.HTTPConduit;
+import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
+import org.apache.hello_world_soap_http.Greeter;
+import org.apache.hello_world_soap_http.SOAPService;
+import org.apache.hello_world_soap_http.types.GreetMeLaterResponse;
+import org.apache.hello_world_soap_http.types.GreetMeResponse;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AsyncHTTPConduitLargeResponseTest extends AbstractBusClientServerTestBase {
+ public static final String PORT = allocatePort(AsyncHTTPConduitLargeResponseTest.class);
+ public static final String PORT_INV = allocatePort(AsyncHTTPConduitLargeResponseTest.class, 2);
+
+ static Endpoint ep;
+ static String request;
+ static Greeter g;
+
+ @BeforeClass
+ public static void start() throws Exception {
+ Bus b = createStaticBus();
+ b.setProperty(AsyncHTTPConduit.USE_ASYNC, AsyncHTTPConduitFactory.UseAsyncPolicy.ALWAYS);
+ b.setProperty("org.apache.cxf.transport.http.async.MAX_CONNECTIONS", 501);
+
+ BusFactory.setThreadDefaultBus(b);
+
+ ep = Endpoint.publish("http://localhost:" + PORT + "/SoapContext/SoapPort",
+ new org.apache.hello_world_soap_http.GreeterImpl() {
+ public String greetMeLater(long cnt) {
+ //use the continuations so the async client can
+ //have a ton of connections, use less threads
+ //
+ //mimic a slow server by delaying somewhere between
+ //1 and 2 seconds, with a preference of delaying the earlier
+ //requests longer to create a sort of backlog/contention
+ //with the later requests
+ ContinuationProvider p = (ContinuationProvider)
+ getContext().getMessageContext().get(ContinuationProvider.class.getName());
+ Continuation c = p.getContinuation();
+ if (c.isNew()) {
+ if (cnt < 0) {
+ c.suspend(-cnt);
+ } else {
+ c.suspend(2000 - (cnt % 1000));
+ }
+ return null;
+ }
+ return "Hello, finally! " + cnt;
+ }
+ public String greetMe(String me) {
+ return "Hello " + String.join("", Collections.nCopies(500, me));
+ }
+ });
+
+ StringBuilder builder = new StringBuilder("NaNaNa");
+ for (int x = 0; x < 50; x++) {
+ builder.append(" NaNaNa ");
+ }
+ request = builder.toString();
+
+ URL wsdl = AsyncHTTPConduitLargeResponseTest.class.getResource("/wsdl/hello_world_services.wsdl");
+ assertNotNull("WSDL is null", wsdl);
+
+ SOAPService service = new SOAPService();
+ assertNotNull("Service is null", service);
+
+ g = service.getSoapPort();
+ assertNotNull("Port is null", g);
+ }
+
+ @AfterClass
+ public static void stop() throws Exception {
+ ((java.io.Closeable)g).close();
+ ep.stop();
+ ep = null;
+ }
+
+ @Test
+ public void testCall() throws Exception {
+ updateAddressPort(g, PORT);
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), g.greetMe(request));
+ HTTPConduit c = (HTTPConduit)ClientProxy.getClient(g).getConduit();
+ HTTPClientPolicy cp = new HTTPClientPolicy();
+ cp.setAllowChunking(false);
+ c.setClient(cp);
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), g.greetMe(request));
+ }
+ @Test
+ public void testCallAsync() throws Exception {
+ updateAddressPort(g, PORT);
+ GreetMeResponse resp = (GreetMeResponse)g.greetMeAsync(request, new AsyncHandler<GreetMeResponse>() {
+ public void handleResponse(Response<GreetMeResponse> res) {
+ try {
+ res.get().getResponseType();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+ }).get();
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), resp.getResponseType());
+
+ g.greetMeLaterAsync(1000, new AsyncHandler<GreetMeLaterResponse>() {
+ public void handleResponse(Response<GreetMeLaterResponse> res) {
+ }
+ }).get();
+ }
+
+ @Test
+ public void testCallAsyncCallbackInvokedOnlyOnce() throws Exception {
+ // This test is especially targeted for RHEL 6.8
+ updateAddressPort(g, PORT_INV);
+ int repeat = 100;
+ final AtomicInteger count = new AtomicInteger(0);
+ for (int i = 0; i < repeat; i++) {
+ try {
+ g.greetMeAsync(request, new AsyncHandler<GreetMeResponse>() {
+ public void handleResponse(Response<GreetMeResponse> res) {
+ count.incrementAndGet();
+ }
+ }).get();
+ } catch (Exception e) {
+ }
+ }
+ Thread.sleep(1000);
+ assertEquals("Callback should be invoked only once per request", repeat, count.intValue());
+ }
+}
\ No newline at end of file
diff --git a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/SharedInputBuffer.java b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/SharedInputBuffer.java
index 7b7e705869..0ba6b2c936 100644
--- a/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/SharedInputBuffer.java
+++ b/rt/transports/http-hc5/src/main/java/org/apache/cxf/transport/http/asyncclient/hc5/SharedInputBuffer.java
@@ -44,6 +44,7 @@ public class SharedInputBuffer extends ExpandableBuffer {
private final ReentrantLock lock;
private final Condition condition;
+ private final Condition suspendInput;
private volatile boolean shutdown;
private volatile boolean endOfStream;
@@ -54,6 +55,7 @@ public class SharedInputBuffer extends ExpandableBuffer {
super(buffersize);
this.lock = new ReentrantLock();
this.condition = this.lock.newCondition();
+ this.suspendInput = this.lock.newCondition();
}
public void reset() {
@@ -73,6 +75,7 @@ public class SharedInputBuffer extends ExpandableBuffer {
if (this.shutdown) {
return -1;
}
+
this.lock.lock();
try {
setInputMode();
@@ -83,15 +86,24 @@ public class SharedInputBuffer extends ExpandableBuffer {
totalRead += bytesRead;
}
}
+
//read more
- while ((bytesRead = transfer(buffer, buffer())) > 0) {
+ while ((bytesRead = transfer(buffer)) > 0) {
totalRead += bytesRead;
}
-
+
if (last) {
this.endOfStream = true;
}
-
+
+ if (!buffer().hasRemaining() && !this.endOfStream) {
+ try {
+ suspendInput.await();
+ } catch (InterruptedException ex) {
+ throw new IOException("Interrupted while waiting buffer to be drained ");
+ }
+ }
+
this.condition.signalAll();
if (totalRead > 0) {
@@ -153,6 +165,8 @@ public class SharedInputBuffer extends ExpandableBuffer {
if (this.shutdown) {
throw new InterruptedIOException("Input operation aborted");
}
+
+ this.suspendInput.signalAll();
this.condition.await();
}
} catch (InterruptedException ex) {
@@ -262,10 +276,15 @@ public class SharedInputBuffer extends ExpandableBuffer {
private int transfer(ByteBuffer from, ByteBuffer to) {
int transfer = Math.min(to.remaining(), from.remaining());
+
if (from.remaining() == 0) {
return -1;
}
+ if (transfer == 0) {
+ return transfer;
+ }
+
// use a duplicated buffer so we don't disrupt the limit of the original buffer
final ByteBuffer tmp = from.duplicate();
tmp.limit(tmp.position() + transfer);
@@ -275,5 +294,19 @@ public class SharedInputBuffer extends ExpandableBuffer {
from.position(from.position() + transfer);
return transfer;
}
+
+ private int transfer(ByteBuffer from) {
+ ensureCapacity(from);
+ return transfer(from, buffer());
+ }
+
+ private void ensureCapacity(ByteBuffer source) {
+ if (buffer().remaining() >= source.remaining()) {
+ return;
+ } else {
+ final int adjustment = source.remaining() - buffer().remaining();
+ ensureCapacity(buffer().capacity() + adjustment);
+ }
+ }
}
diff --git a/rt/transports/http-hc5/src/test/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitLargeResponseTest.java b/rt/transports/http-hc5/src/test/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitLargeResponseTest.java
new file mode 100644
index 0000000000..d221d34a7f
--- /dev/null
+++ b/rt/transports/http-hc5/src/test/java/org/apache/cxf/transport/http/asyncclient/hc5/AsyncHTTPConduitLargeResponseTest.java
@@ -0,0 +1,172 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.transport.http.asyncclient.hc5;
+
+
+import java.net.URL;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.xml.ws.AsyncHandler;
+import javax.xml.ws.Endpoint;
+import javax.xml.ws.Response;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.continuations.Continuation;
+import org.apache.cxf.continuations.ContinuationProvider;
+import org.apache.cxf.frontend.ClientProxy;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.transport.http.HTTPConduit;
+import org.apache.cxf.transport.http.HTTPConduitFactory;
+import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
+import org.apache.hello_world_soap_http.Greeter;
+import org.apache.hello_world_soap_http.SOAPService;
+import org.apache.hello_world_soap_http.types.GreetMeLaterResponse;
+import org.apache.hello_world_soap_http.types.GreetMeResponse;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AsyncHTTPConduitLargeResponseTest extends AbstractBusClientServerTestBase {
+ public static final String PORT = allocatePort(AsyncHTTPConduitLargeResponseTest.class);
+ public static final String PORT_INV = allocatePort(AsyncHTTPConduitLargeResponseTest.class, 2);
+
+ static Endpoint ep;
+ static String request;
+ static Greeter g;
+
+ @BeforeClass
+ public static void start() throws Exception {
+ Bus b = createStaticBus();
+ b.setProperty(AsyncHTTPConduit.USE_ASYNC, AsyncHTTPConduitFactory.UseAsyncPolicy.ALWAYS);
+ b.setProperty("org.apache.cxf.transport.http.async.MAX_CONNECTIONS", 501);
+
+ BusFactory.setThreadDefaultBus(b);
+
+ AsyncHTTPConduitFactory hcf = (AsyncHTTPConduitFactory)b.getExtension(HTTPConduitFactory.class);
+ assertEquals(501, hcf.getMaxConnections());
+
+ ep = Endpoint.publish("http://localhost:" + PORT + "/SoapContext/SoapPort",
+ new org.apache.hello_world_soap_http.GreeterImpl() {
+ public String greetMeLater(long cnt) {
+ //use the continuations so the async client can
+ //have a ton of connections, use less threads
+ //
+ //mimic a slow server by delaying somewhere between
+ //1 and 2 seconds, with a preference of delaying the earlier
+ //requests longer to create a sort of backlog/contention
+ //with the later requests
+ ContinuationProvider p = (ContinuationProvider)
+ getContext().getMessageContext().get(ContinuationProvider.class.getName());
+ Continuation c = p.getContinuation();
+ if (c.isNew()) {
+ if (cnt < 0) {
+ c.suspend(-cnt);
+ } else {
+ c.suspend(2000 - (cnt % 1000));
+ }
+ return null;
+ }
+ return "Hello, finally! " + cnt;
+ }
+ public String greetMe(String me) {
+ return "Hello " + String.join("", Collections.nCopies(500, me));
+ }
+ });
+
+ StringBuilder builder = new StringBuilder("NaNaNa");
+ for (int x = 0; x < 50; x++) {
+ builder.append(" NaNaNa ");
+ }
+ request = builder.toString();
+
+ URL wsdl = AsyncHTTPConduitLargeResponseTest.class.getResource("/wsdl/hello_world_services.wsdl");
+ assertNotNull("WSDL is null", wsdl);
+
+ SOAPService service = new SOAPService();
+ assertNotNull("Service is null", service);
+
+ g = service.getSoapPort();
+ assertNotNull("Port is null", g);
+ }
+
+ @AfterClass
+ public static void stop() throws Exception {
+ ((java.io.Closeable)g).close();
+ ep.stop();
+ ep = null;
+ }
+
+ @Test
+ public void testCall() throws Exception {
+ updateAddressPort(g, PORT);
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), g.greetMe(request));
+ HTTPConduit c = (HTTPConduit)ClientProxy.getClient(g).getConduit();
+ HTTPClientPolicy cp = new HTTPClientPolicy();
+ cp.setAllowChunking(false);
+ c.setClient(cp);
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), g.greetMe(request));
+ }
+ @Test
+ public void testCallAsync() throws Exception {
+ updateAddressPort(g, PORT);
+ GreetMeResponse resp = (GreetMeResponse)g.greetMeAsync(request, new AsyncHandler<GreetMeResponse>() {
+ public void handleResponse(Response<GreetMeResponse> res) {
+ try {
+ res.get().getResponseType();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+ }).get();
+ assertEquals("Hello " + String.join("", Collections.nCopies(500, request)), resp.getResponseType());
+
+ g.greetMeLaterAsync(1000, new AsyncHandler<GreetMeLaterResponse>() {
+ public void handleResponse(Response<GreetMeLaterResponse> res) {
+ }
+ }).get();
+ }
+
+ @Test
+ public void testCallAsyncCallbackInvokedOnlyOnce() throws Exception {
+ // This test is especially targeted for RHEL 6.8
+ updateAddressPort(g, PORT_INV);
+ int repeat = 100;
+ final AtomicInteger count = new AtomicInteger(0);
+ for (int i = 0; i < repeat; i++) {
+ try {
+ g.greetMeAsync(request, new AsyncHandler<GreetMeResponse>() {
+ public void handleResponse(Response<GreetMeResponse> res) {
+ count.incrementAndGet();
+ }
+ }).get();
+ } catch (Exception e) {
+ }
+ }
+ Thread.sleep(1000);
+ assertEquals("Callback should be invoked only once per request", repeat, count.intValue());
+ }
+}
\ No newline at end of file
[cxf] 02/02: Recording .gitmergeinfo Changes
Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch 3.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 737a98679fd553e4e2cb1fb908680a4db0c90406
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Sat Apr 1 15:22:55 2023 -0400
Recording .gitmergeinfo Changes
---
.gitmergeinfo | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.gitmergeinfo b/.gitmergeinfo
index 5130754d73..3777a8d114 100644
--- a/.gitmergeinfo
+++ b/.gitmergeinfo
@@ -34,6 +34,7 @@ B 1b903fea249eaae3d40db91fccde6c6207c0bfaa
B 1cb2a5fe44e390cf2106b07d8b6a8b5fffcda3e7
B 1cc69bd3ae56bcb514d706d876f9b97135253d34
B 1d90a3448af34dcd637a52aee4ae0f5be6c43b04
+B 1f0e621d06eaf29e2a024ee2188959625a81f34d
B 1f50286782f1b005d857a858af05555017e8e29a
B 1fe6d8a72c2253bcabc03fc124275a89d7a685d6
B 22cb503d5380dd421938c579612360ef58b95b33
@@ -54,6 +55,7 @@ B 3176a94564529b8c233672e507c73dfbfca6ca00
B 320534fa741aa00834c1d35a46efd3eedeb7229a
B 32b2b5b54934c2954f9f7205bfdb00ae3f79d5f2
B 332a02450fe1620daeaa2b65c0716e337d49095a
+B 336852177ffcca439d5c9342ede0a71f43c3e15a
B 33e1cd0ca132cf0012b126d71745c114eb0a7325
B 34a8f691a1926a26d2ecefa527eaf76f68b98def
B 36c7be2b56de371bf2f9af5c10f322973d3b2a59
@@ -302,6 +304,7 @@ M 138aa4d6685f4d833c040c1a7f92ac86cbe49a6a
M 14279ec49cfcea67e74bba6a909daa6d574f047d
M 148f59425afbe5c95786d5f65912cd7c3c1fcc9c
M 15185954d24578b3a848afe5bf258d9774ff03b6
+M 1549151899e869e69bbfec3ac9bbeae21961f368
M 15b08e39c147169757c162b1bc6a9f8916ad0fed
M 16df606f5ea05ad8fe5c3ab54cc4e3684067d84d
M 1820f1a94b08ba840c3868ac3e1f3b7751540a08
@@ -414,6 +417,7 @@ M 58f9b263887ccec9a07292dbd6efc855cd4bb7d0
M 5936bedea27333fd13a94a8a6a881c9246b6220e
M 59ef61b0288e1e9b05f0bdc3e78f8b55eb76ad88
M 59f2f96920558470d08483e924a9b088a85beaad
+M 5a1c6b67df2939180c74e3aa63d5cfbd643e0815
M 5b7a1e33e54a33fb5a060e7f5a5ed9727e06f13e
M 5b850b3fa3760c9863ecd49855efeb0612003138
M 5c80577035b770eb12a233cc4736ba43a4cf835b
@@ -422,6 +426,7 @@ M 5cbed0aaa0160a8588cca6d8a2290a61bba2cdbf
M 5cdd7e21e3f0cb31d6ff89380af3cd6330d0ea9d
M 5dbaa0da72400f993f9bcc8310c153b04d712205
M 5df02724d93f6a65ee790136e9c50be350748c26
+M 5e04cc3ebcbf8cadc7dd7160fd9d5778e44b8861
M 5e5b6af86a05381c3f972f28f9191a70687601e2
M 5e7c07a511221e498c3fdd4ccfef8690a547e323
M 5f3205a8aefbca21b695a74cd71fe3c55b95e0a6
@@ -532,6 +537,7 @@ M a2a48bd789e66ffae82d35d156d02e58fbb4bb2b
M a2fc449969afa082065b1be644a3af890abcc1db
M a3a49c7ee82f83bc683b898594d5c4948578f25a
M a40e65f3fa075e080aff78123ab76eb2dbe46048
+M a423df8310f61ae78d677105de0e3562fbd5000c
M a5699a05bd53de364ac3282fc6a4927ee93e0afc
M a59dff0ba50418ced57ae8ef425f96df78af9f5c
M a615cc34ee974469e94c8a0086ec2ed0c84991fa