diff --git a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/HttpDecoder.java b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/HttpDecoder.java index 2f0ced8..2c40c1f 100644 --- a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/HttpDecoder.java +++ b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/HttpDecoder.java @@ -31,7 +31,7 @@ public class HttpDecoder extends ChannelInboundHandlerAdapter { while (buf.isReadable() && current != null && current.content == null) - current = current.next.get((char) buf.readByte()); + current = current.next.get(buf.readByte()); if (current == null || current.content == null) return; // Method identified, this is HTTP! passOn = false; diff --git a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/ClaimPool.java b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/ClaimPool.java index 935ed15..7157e9e 100644 --- a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/ClaimPool.java +++ b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/ClaimPool.java @@ -1,37 +1,68 @@ package io.gitlab.jfronny.libjf.mainhttp.impl.util; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Objects; public class ClaimPool { - private final List content = new LinkedList<>(); + private Claim top = null; public Claim claim(T value) { - return new Claim(value); + synchronized (this) { + return top = new Claim(value, top); + } } public T getTopmost() { - return content.isEmpty() ? null : content.get(content.size() - 1).value; + Claim tp = top; + return tp == null ? null : tp.value; } public boolean isEmpty() { - return content.isEmpty(); + return top == null; } public class Claim { + private Claim prev, next; private final T value; - private final AtomicBoolean active = new AtomicBoolean(true); + private boolean active = true; - private Claim(T value) { + private Claim(T value, Claim prev) { + // This is synchronized on the pool, so we can safely read top this.value = value; - content.add(this); + if (prev != null) { + this.prev = prev; + prev.next = this; + } } public void release() { - if (!active.getAndSet(false)) - throw new UnsupportedOperationException("Cannot release claim that is already released"); - content.remove(this); + // By synchronizing from left to right, we ensure that only claims to our left can be blocked by this claim, + // meaning that the rightmost claim can always be released, preventing deadlocks + + // If there is no previous object, prev cannot change, so we can safely read it without synchronization + // (which is effectively what is caused by synchronizing on this twice) + synchronized (Objects.requireNonNullElse(prev, this)) { + synchronized (this) { + // If there is no next object, ClaimPool might add a new one, so we need to synchronize on it + synchronized (Objects.requireNonNullElse(next, ClaimPool.this)) { + if (!active) throw new UnsupportedOperationException("Cannot release claim that is already released"); + active = false; + if (prev != null) prev.next = next; + if (next != null) next.prev = prev; + if (top == this) top = prev; + } + } + } } } -} + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ClaimPool{"); + Claim current = top; + while (current != null) { + sb.append(current.value).append(" -> "); + current = current.prev; + } + return sb.append('}').toString(); + } +} \ No newline at end of file diff --git a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/Trie.java b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/Trie.java index 553c5c8..bf6d00d 100644 --- a/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/Trie.java +++ b/libjf-mainhttp-v0/src/main/java/io/gitlab/jfronny/libjf/mainhttp/impl/util/Trie.java @@ -1,7 +1,7 @@ package io.gitlab.jfronny.libjf.mainhttp.impl.util; -import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap; -import it.unimi.dsi.fastutil.chars.Char2ObjectMap; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectArrayMap; +import it.unimi.dsi.fastutil.bytes.Byte2ObjectMap; import java.util.List; import java.util.Map; @@ -9,11 +9,11 @@ import java.util.function.Function; import java.util.stream.Collectors; public class Trie { - public final Char2ObjectMap> next; + public final Byte2ObjectMap> next; public T content; public Trie() { - this.next = new Char2ObjectArrayMap<>(); + this.next = new Byte2ObjectArrayMap<>(); } public void add(Map next) { @@ -22,7 +22,9 @@ public class Trie { public void add(String key, T value) { if (key.isEmpty()) this.content = value; - else this.next.computeIfAbsent(key.charAt(0), k -> new Trie<>()) + char c = key.charAt(0); + if (c > 255) throw new IllegalArgumentException("Invalid character: " + c); + else this.next.computeIfAbsent((byte) c, k -> new Trie<>()) .add(key.substring(1), value); }