Skip to content

Commit c8acffc

Browse files
committed
Serialize shared HTTP envelope codecs
1 parent f0d09b0 commit c8acffc

4 files changed

Lines changed: 90 additions & 2 deletions

File tree

‎SimpleAPI/src/main/java/com/bencodez/simpleapi/servercomm/http/HttpBackendTransportConnector.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ private HttpBackendTransportConnector(HttpClientCredentialStore.HttpClientProfil
122122
if (profile == null || credential == null || onEnvelope == null || wireCodec == null)
123123
throw new IllegalArgumentException("HTTP backend transport configuration is invalid");
124124
if (!matchesCredential(profile, credential)) throw new IllegalArgumentException("HTTP client certificate does not match transport profile");
125-
this.profile = profile; this.serverId = profile.serverId(); this.onEnvelope = onEnvelope; this.wireCodec = wireCodec;
125+
this.profile = profile; this.serverId = profile.serverId(); this.onEnvelope = onEnvelope;
126+
this.wireCodec = HttpEnvelopeWireCodec.serialized(wireCodec);
126127
this.credential = credential;
127128
this.credentialDirectory = credentialDirectory;
128129
HttpInboundDeliveryStore loadedInbound = null, loadedAcknowledgements = null;

‎SimpleAPI/src/main/java/com/bencodez/simpleapi/servercomm/http/HttpEnvelopeWireCodec.java‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.bencodez.simpleapi.servercomm.http;
22

3+
import java.util.Objects;
4+
35
import com.bencodez.simpleapi.servercomm.codec.JsonEnvelope;
46

57
/**
@@ -20,6 +22,12 @@ static HttpEnvelopeWireCodec identity() {
2022
return Identity.INSTANCE;
2123
}
2224

25+
/** Serializes a codec shared by concurrent HTTP sessions. */
26+
static HttpEnvelopeWireCodec serialized(HttpEnvelopeWireCodec codec) {
27+
Objects.requireNonNull(codec, "wireCodec");
28+
return codec == Identity.INSTANCE ? codec : new Serialized(codec);
29+
}
30+
2331
final class Identity implements HttpEnvelopeWireCodec {
2432
private static final Identity INSTANCE = new Identity();
2533

@@ -29,4 +37,26 @@ private Identity() { }
2937

3038
@Override public JsonEnvelope decode(JsonEnvelope envelope) { return envelope; }
3139
}
40+
41+
final class Serialized implements HttpEnvelopeWireCodec {
42+
private final HttpEnvelopeWireCodec delegate;
43+
44+
private Serialized(HttpEnvelopeWireCodec delegate) {
45+
this.delegate = delegate;
46+
}
47+
48+
@Override
49+
public JsonEnvelope encode(JsonEnvelope envelope) {
50+
synchronized (delegate) {
51+
return delegate.encode(envelope);
52+
}
53+
}
54+
55+
@Override
56+
public JsonEnvelope decode(JsonEnvelope envelope) {
57+
synchronized (delegate) {
58+
return delegate.decode(envelope);
59+
}
60+
}
61+
}
3262
}

‎SimpleAPI/src/main/java/com/bencodez/simpleapi/servercomm/http/HttpProxyTransportServer.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ public HttpProxyTransportServer(InetSocketAddress bind, HttpTlsIdentity identity
133133
throw new IllegalArgumentException("HTTP transport configuration is required");
134134
if (wireCodec == null || nanoTime == null) throw new IllegalArgumentException("HTTP transport codec and clock are required");
135135
this.identity = identity; this.authority = authority; this.onEnvelope = onEnvelope;
136-
this.onAcknowledged = onAcknowledged; this.wireCodec = wireCodec; this.nanoTime = nanoTime;
136+
this.onAcknowledged = onAcknowledged;
137+
this.wireCodec = HttpEnvelopeWireCodec.serialized(wireCodec);
138+
this.nanoTime = nanoTime;
137139
durableOutgoing = outgoingDirectory == null ? null : new DurableOutgoingQueue(outgoingDirectory);
138140
HttpsServer createdServer = null;
139141
ThreadPoolExecutor createdListener = null, createdHandler = null;

‎SimpleAPI/src/test/java/com/bencodez/simpleapi/servercomm/http/HttpEnvelopeWireCodecTest.java‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,69 @@
1313
import java.util.LinkedHashMap;
1414
import java.util.UUID;
1515
import java.util.concurrent.CountDownLatch;
16+
import java.util.concurrent.Executors;
1617
import java.util.concurrent.TimeUnit;
18+
import java.util.concurrent.atomic.AtomicInteger;
1719
import java.util.concurrent.atomic.AtomicReference;
1820
import org.junit.jupiter.api.Test;
1921
import org.junit.jupiter.api.io.TempDir;
2022

2123
class HttpEnvelopeWireCodecTest {
2224
@TempDir Path directory;
2325

26+
@Test
27+
void serializedWrappersCoordinateConcurrentAccessToOneCodec() throws Exception {
28+
CountDownLatch firstEntered = new CountDownLatch(1);
29+
CountDownLatch releaseFirst = new CountDownLatch(1);
30+
AtomicInteger active = new AtomicInteger();
31+
AtomicInteger maximumActive = new AtomicInteger();
32+
HttpEnvelopeWireCodec delegate = new HttpEnvelopeWireCodec() {
33+
private JsonEnvelope apply(JsonEnvelope envelope) {
34+
int current = active.incrementAndGet();
35+
maximumActive.accumulateAndGet(current, Math::max);
36+
try {
37+
if (current == 1) {
38+
firstEntered.countDown();
39+
assertTrue(releaseFirst.await(5, TimeUnit.SECONDS));
40+
}
41+
return envelope;
42+
} catch (InterruptedException interrupted) {
43+
Thread.currentThread().interrupt();
44+
throw new IllegalStateException(interrupted);
45+
} finally {
46+
active.decrementAndGet();
47+
}
48+
}
49+
50+
@Override public JsonEnvelope encode(JsonEnvelope envelope) { return apply(envelope); }
51+
@Override public JsonEnvelope decode(JsonEnvelope envelope) { return apply(envelope); }
52+
};
53+
HttpEnvelopeWireCodec first = HttpEnvelopeWireCodec.serialized(delegate);
54+
HttpEnvelopeWireCodec second = HttpEnvelopeWireCodec.serialized(delegate);
55+
JsonEnvelope envelope = JsonEnvelope.builder("test").build();
56+
var executor = Executors.newFixedThreadPool(2);
57+
try {
58+
var firstCall = executor.submit(() -> first.encode(envelope));
59+
assertTrue(firstEntered.await(5, TimeUnit.SECONDS));
60+
CountDownLatch secondAttempted = new CountDownLatch(1);
61+
var secondCall = executor.submit(() -> {
62+
secondAttempted.countDown();
63+
return second.decode(envelope);
64+
});
65+
assertTrue(secondAttempted.await(5, TimeUnit.SECONDS));
66+
assertThrows(java.util.concurrent.TimeoutException.class,
67+
() -> secondCall.get(100, TimeUnit.MILLISECONDS));
68+
assertEquals(1, maximumActive.get());
69+
releaseFirst.countDown();
70+
assertEquals(envelope, firstCall.get(5, TimeUnit.SECONDS));
71+
assertEquals(envelope, secondCall.get(5, TimeUnit.SECONDS));
72+
assertEquals(1, maximumActive.get());
73+
} finally {
74+
releaseFirst.countDown();
75+
executor.shutdownNow();
76+
}
77+
}
78+
2479
@Test
2580
void durableProxyQueueKeepsSemanticEnvelopeAcrossCodecChange() throws Exception {
2681
Path outgoing = directory.resolve("outgoing");

0 commit comments

Comments
 (0)