This is an automated email from the ASF dual-hosted git repository.

xiaoyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new a3909ea  [ISSUE #3026] part1: support MemoryLimitedLinkedBlockingQueue 
(#3043)
a3909ea is described below

commit a3909eaa0a0274f80d335367d57c6f180e0406a0
Author: dragon-zhang <[email protected]>
AuthorDate: Wed Mar 16 10:02:25 2022 +0800

    [ISSUE #3026] part1: support MemoryLimitedLinkedBlockingQueue (#3043)
    
    * [ISSUE #3026] part1: support MemoryLimitedLinkedBlockingQueue
    
    * fix code style
    
    * fix code style
    
    * fix code style
    
    * fix code style
---
 .../MemoryLimitedLinkedBlockingQueue.java          | 138 +++++++++
 .../shenyu/common/concurrent/MemoryLimiter.java    | 340 +++++++++++++++++++++
 .../MemoryLimitedLinkedBlockingQueueTest.java      |  43 +++
 3 files changed, 521 insertions(+)

diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueue.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueue.java
new file mode 100644
index 0000000..dc64d99
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueue.java
@@ -0,0 +1,138 @@
+/*
+ * 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.shenyu.common.concurrent;
+
+import java.lang.instrument.Instrumentation;
+import java.util.Collection;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Can completely solve the OOM problem caused by {@link 
java.util.concurrent.LinkedBlockingQueue}.
+ */
+public class MemoryLimitedLinkedBlockingQueue<E> extends 
LinkedBlockingQueue<E> {
+
+    private static final long serialVersionUID = -6106022470621447542L;
+
+    private final MemoryLimiter memoryLimiter;
+
+    public MemoryLimitedLinkedBlockingQueue(final Instrumentation inst) {
+        this(Integer.MAX_VALUE, inst);
+    }
+
+    public MemoryLimitedLinkedBlockingQueue(final long memoryLimit,
+                                            final Instrumentation inst) {
+        super(Integer.MAX_VALUE);
+        this.memoryLimiter = new MemoryLimiter(memoryLimit, inst);
+    }
+
+    public MemoryLimitedLinkedBlockingQueue(final Collection<? extends E> c,
+                                            final long memoryLimit,
+                                            final Instrumentation inst) {
+        super(c);
+        this.memoryLimiter = new MemoryLimiter(memoryLimit, inst);
+    }
+
+    /**
+     * set the memory limit.
+     *
+     * @param memoryLimit the memory limit
+     */
+    public void setMemoryLimit(final long memoryLimit) {
+        memoryLimiter.setMemoryLimit(memoryLimit);
+    }
+
+    /**
+     * get the memory limit.
+     *
+     * @return the memory limit
+     */
+    public long getMemoryLimit() {
+        return memoryLimiter.getMemoryLimit();
+    }
+
+    /**
+     * get the current memory.
+     *
+     * @return the current memory
+     */
+    public long getCurrentMemory() {
+        return memoryLimiter.getCurrentMemory();
+    }
+
+    /**
+     * get the current remain memory.
+     *
+     * @return the current remain memory
+     */
+    public long getCurrentRemainMemory() {
+        return memoryLimiter.getCurrentRemainMemory();
+    }
+
+    @Override
+    public void put(final E e) throws InterruptedException {
+        memoryLimiter.acquireInterruptibly(e);
+        super.put(e);
+    }
+
+    @Override
+    public boolean offer(final E e, final long timeout, final TimeUnit unit) 
throws InterruptedException {
+        return memoryLimiter.acquire(e, timeout, unit) && super.offer(e, 
timeout, unit);
+    }
+
+    @Override
+    public boolean offer(final E e) {
+        return memoryLimiter.acquire(e) && super.offer(e);
+    }
+
+    @Override
+    public E take() throws InterruptedException {
+        final E e = super.take();
+        memoryLimiter.releaseInterruptibly(e);
+        return e;
+    }
+
+    @Override
+    public E poll(final long timeout, final TimeUnit unit) throws 
InterruptedException {
+        final E e = super.poll(timeout, unit);
+        memoryLimiter.releaseInterruptibly(e, timeout, unit);
+        return e;
+    }
+
+    @Override
+    public E poll() {
+        final E e = super.poll();
+        memoryLimiter.release(e);
+        return e;
+    }
+
+    @Override
+    public boolean remove(final Object o) {
+        final boolean success = super.remove(o);
+        if (success) {
+            memoryLimiter.release(o);
+        }
+        return success;
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+        memoryLimiter.reset();
+    }
+}
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimiter.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimiter.java
new file mode 100644
index 0000000..2d3e62f
--- /dev/null
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/concurrent/MemoryLimiter.java
@@ -0,0 +1,340 @@
+/*
+ * 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.shenyu.common.concurrent;
+
+import java.lang.instrument.Instrumentation;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * memory limiter.
+ */
+public class MemoryLimiter {
+
+    private final Instrumentation inst;
+
+    private long memoryLimit;
+
+    private final LongAdder memory = new LongAdder();
+
+    private final ReentrantLock acquireLock = new ReentrantLock();
+
+    private final Condition notLimited = acquireLock.newCondition();
+
+    private final ReentrantLock releaseLock = new ReentrantLock();
+
+    private final Condition notEmpty = releaseLock.newCondition();
+
+    public MemoryLimiter(final Instrumentation inst) {
+        this(Integer.MAX_VALUE, inst);
+    }
+
+    public MemoryLimiter(final long memoryLimit, final Instrumentation inst) {
+        if (memoryLimit <= 0) {
+            throw new IllegalArgumentException();
+        }
+        this.memoryLimit = memoryLimit;
+        this.inst = inst;
+    }
+
+    /**
+     * set the memory limit.
+     *
+     * @param memoryLimit the memory limit
+     */
+    public void setMemoryLimit(final long memoryLimit) {
+        if (memoryLimit <= 0) {
+            throw new IllegalArgumentException();
+        }
+        this.memoryLimit = memoryLimit;
+    }
+
+    /**
+     * get the memory limit.
+     *
+     * @return the memory limit
+     */
+    public long getMemoryLimit() {
+        return memoryLimit;
+    }
+
+    /**
+     * get the current memory.
+     *
+     * @return the current memory
+     */
+    public long getCurrentMemory() {
+        return memory.sum();
+    }
+
+    /**
+     * get the current remain memory.
+     *
+     * @return the current remain memory
+     */
+    public long getCurrentRemainMemory() {
+        return getMemoryLimit() - getCurrentMemory();
+    }
+
+    private void signalNotEmpty() {
+        releaseLock.lock();
+        try {
+            notEmpty.signal();
+        } finally {
+            releaseLock.unlock();
+        }
+    }
+
+    private void signalNotLimited() {
+        acquireLock.lock();
+        try {
+            notLimited.signal();
+        } finally {
+            acquireLock.unlock();
+        }
+    }
+
+    /**
+     * Locks to prevent both acquires and releases.
+     */
+    private void fullyLock() {
+        acquireLock.lock();
+        releaseLock.lock();
+    }
+
+    /**
+     * Unlocks to allow both acquires and releases.
+     */
+    private void fullyUnlock() {
+        releaseLock.unlock();
+        acquireLock.unlock();
+    }
+
+    /**
+     * acquire memory by {@link Object}.
+     * this method does not respond to interrupts.
+     *
+     * @param o memory size to be applied by calculating
+     * @return true if acquire success
+     */
+    public boolean acquire(final Object o) {
+        if (o == null) {
+            throw new NullPointerException();
+        }
+        if (memory.sum() >= memoryLimit) {
+            return false;
+        }
+        acquireLock.lock();
+        try {
+            final long sum = memory.sum();
+            final long objectSize = inst.getObjectSize(o);
+            if (sum + objectSize >= memoryLimit) {
+                return false;
+            }
+            memory.add(objectSize);
+            if (sum < memoryLimit) {
+                notLimited.signal();
+            }
+        } finally {
+            acquireLock.unlock();
+        }
+        if (memory.sum() > 0) {
+            signalNotEmpty();
+        }
+        return true;
+    }
+
+    /**
+     * acquire memory by {@link Object}.
+     * this method response to interrupts.
+     *
+     * @param o       memory size to be applied by calculating
+     * @param timeout max time to wait
+     * @param unit    time unit
+     * @return true if acquire success
+     * @throws InterruptedException the InterruptedException
+     */
+    public boolean acquire(final Object o, final long timeout,
+                           final TimeUnit unit) throws InterruptedException {
+        if (o == null) {
+            throw new NullPointerException();
+        }
+        long nanos = unit.toNanos(timeout);
+        acquireLock.lockInterruptibly();
+        try {
+            final long objectSize = inst.getObjectSize(o);
+            while (memory.sum() + objectSize >= memoryLimit) {
+                if (nanos <= 0) {
+                    return false;
+                }
+                nanos = notLimited.awaitNanos(nanos);
+            }
+            memory.add(objectSize);
+            if (memory.sum() < memoryLimit) {
+                notLimited.signal();
+            }
+        } finally {
+            acquireLock.unlock();
+        }
+        if (memory.sum() > 0) {
+            signalNotEmpty();
+        }
+        return true;
+    }
+
+    /**
+     * acquire memory by {@link Object}.
+     * this method response to interrupts.
+     *
+     * @param o memory size to be applied by calculating
+     * @throws InterruptedException the InterruptedException
+     */
+    public void acquireInterruptibly(final Object o) throws 
InterruptedException {
+        if (o == null) {
+            throw new NullPointerException();
+        }
+        acquireLock.lockInterruptibly();
+        try {
+            final long sum = memory.sum();
+            final long objectSize = inst.getObjectSize(o);
+            while (sum + objectSize >= memoryLimit) {
+                notLimited.await();
+            }
+            memory.add(objectSize);
+            if (sum < memoryLimit) {
+                notLimited.signal();
+            }
+        } finally {
+            acquireLock.unlock();
+        }
+        if (memory.sum() > 0) {
+            signalNotEmpty();
+        }
+    }
+
+    /**
+     * release memory by {@link Object}.
+     * this method does not respond to interrupts.
+     *
+     * @param o memory size to be applied by calculating
+     */
+    public void release(final Object o) {
+        if (null == o) {
+            return;
+        }
+        if (memory.sum() == 0) {
+            return;
+        }
+        releaseLock.lock();
+        try {
+            final long objectSize = inst.getObjectSize(o);
+            if (memory.sum() > 0) {
+                memory.add(-objectSize);
+                if (memory.sum() > 0) {
+                    notEmpty.signal();
+                }
+            }
+        } finally {
+            releaseLock.unlock();
+        }
+        if (memory.sum() < memoryLimit) {
+            signalNotLimited();
+        }
+    }
+
+    /**
+     * release memory by {@link Object}.
+     * this method response to interrupts.
+     *
+     * @param o memory size to be applied by calculating
+     * @throws InterruptedException the InterruptedException
+     */
+    public void releaseInterruptibly(final Object o) throws 
InterruptedException {
+        if (null == o) {
+            return;
+        }
+        releaseLock.lockInterruptibly();
+        try {
+            final long objectSize = inst.getObjectSize(o);
+            while (memory.sum() == 0) {
+                notEmpty.await();
+            }
+            memory.add(-objectSize);
+            if (memory.sum() > 0) {
+                notEmpty.signal();
+            }
+        } finally {
+            releaseLock.unlock();
+        }
+        if (memory.sum() < memoryLimit) {
+            signalNotLimited();
+        }
+    }
+
+    /**
+     * release memory by {@link Object}.
+     * this method response to interrupts.
+     *
+     * @param o       memory size to be applied by calculating
+     * @param timeout max time to wait
+     * @param unit    time unit
+     * @throws InterruptedException the InterruptedException
+     */
+    public void releaseInterruptibly(final Object o, final long timeout,
+                                     final TimeUnit unit) throws 
InterruptedException {
+        if (null == o) {
+            return;
+        }
+        long nanos = unit.toNanos(timeout);
+        releaseLock.lockInterruptibly();
+        try {
+            final long objectSize = inst.getObjectSize(o);
+            while (memory.sum() == 0) {
+                if (nanos <= 0) {
+                    return;
+                }
+                nanos = notEmpty.awaitNanos(nanos);
+            }
+            memory.add(-objectSize);
+            if (memory.sum() > 0) {
+                notEmpty.signal();
+            }
+        } finally {
+            releaseLock.unlock();
+        }
+        if (memory.sum() < memoryLimit) {
+            signalNotLimited();
+        }
+    }
+
+    /**
+     * reset this MemoryLimiter.
+     */
+    public void reset() {
+        fullyLock();
+        try {
+            if (memory.sumThenReset() < memoryLimit) {
+                notLimited.signal();
+            }
+        } finally {
+            fullyUnlock();
+        }
+    }
+}
diff --git 
a/shenyu-common/src/test/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueueTest.java
 
b/shenyu-common/src/test/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueueTest.java
new file mode 100644
index 0000000..047b983
--- /dev/null
+++ 
b/shenyu-common/src/test/java/org/apache/shenyu/common/concurrent/MemoryLimitedLinkedBlockingQueueTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.shenyu.common.concurrent;
+
+import net.bytebuddy.agent.ByteBuddyAgent;
+import org.junit.jupiter.api.Test;
+
+import java.lang.instrument.Instrumentation;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class MemoryLimitedLinkedBlockingQueueTest {
+    @Test
+    public void test() throws Exception {
+        ByteBuddyAgent.install();
+        final Instrumentation instrumentation = 
ByteBuddyAgent.getInstrumentation();
+        MemoryLimitedLinkedBlockingQueue<Runnable> queue = new 
MemoryLimitedLinkedBlockingQueue<>(1, instrumentation);
+        //an Runnable needs more than 1 byte of space, so it will fail here
+        assertThat(queue.offer(() -> {
+        }), is(false));
+
+        //will success
+        queue.setMemoryLimit(Integer.MAX_VALUE);
+        assertThat(queue.offer(() -> {
+        }), is(true));
+    }
+}

Reply via email to