From 9c8f4e761951bfac0ad50e3bffe92824e5497a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E6=97=A0=E4=B8=A4=E4=B8=B6?= <442367943@qq.com> Date: Tue, 21 Aug 2018 13:26:50 +0800 Subject: [PATCH 01/12] HashedWheelTimer (#1973) * * HashedWheelTimer to check timeout future * when the future is done, do not set it is timeout --- .../dubbo/common/timer/HashedWheelTimer.java | 805 ++++++++++++++++++ .../apache/dubbo/common/timer/Timeout.java | 55 ++ .../org/apache/dubbo/common/timer/Timer.java | 48 ++ .../apache/dubbo/common/timer/TimerTask.java | 34 + .../dubbo/common/utils/StringUtils.java | 14 + .../common/timer/HashedWheelTimerTest.java | 72 ++ .../exchange/support/DefaultFuture.java | 82 +- .../support/header/HeaderExchangeChannel.java | 2 +- .../exchange/support/DefaultFutureTest.java | 124 +++ .../rpc/protocol/thrift/ThriftCodecTest.java | 4 +- 10 files changed, 1202 insertions(+), 38 deletions(-) create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timeout.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timer.java create mode 100644 dubbo-common/src/main/java/org/apache/dubbo/common/timer/TimerTask.java create mode 100644 dubbo-common/src/test/java/org/apache/dubbo/common/timer/HashedWheelTimerTest.java create mode 100644 dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/DefaultFutureTest.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java new file mode 100644 index 00000000000..db5c43fee97 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java @@ -0,0 +1,805 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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.dubbo.common.timer; + +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; + +import java.util.Queue; +import java.util.Set; +import java.util.HashSet; +import java.util.Collections; +import java.util.Locale; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A {@link Timer} optimized for approximated I/O timeout scheduling. + * + *

Tick Duration

+ *

+ * As described with 'approximated', this timer does not execute the scheduled + * {@link TimerTask} on time. {@link HashedWheelTimer}, on every tick, will + * check if there are any {@link TimerTask}s behind the schedule and execute + * them. + *

+ * You can increase or decrease the accuracy of the execution timing by + * specifying smaller or larger tick duration in the constructor. In most + * network applications, I/O timeout does not need to be accurate. Therefore, + * the default tick duration is 100 milliseconds and you will not need to try + * different configurations in most cases. + * + *

Ticks per Wheel (Wheel Size)

+ *

+ * {@link HashedWheelTimer} maintains a data structure called 'wheel'. + * To put simply, a wheel is a hash table of {@link TimerTask}s whose hash + * function is 'dead line of the task'. The default number of ticks per wheel + * (i.e. the size of the wheel) is 512. You could specify a larger value + * if you are going to schedule a lot of timeouts. + * + *

Do not create many instances.

+ *

+ * {@link HashedWheelTimer} creates a new thread whenever it is instantiated and + * started. Therefore, you should make sure to create only one instance and + * share it across your application. One of the common mistakes, that makes + * your application unresponsive, is to create a new instance for every connection. + * + *

Implementation Details

+ *

+ * {@link HashedWheelTimer} is based on + * George Varghese and + * Tony Lauck's paper, + * 'Hashed + * and Hierarchical Timing Wheels: data structures to efficiently implement a + * timer facility'. More comprehensive slides are located + * here. + */ +public class HashedWheelTimer implements Timer { + + /** + * may be in spi? + */ + public static final String NAME = "hased"; + + private static final Logger logger = LoggerFactory.getLogger(HashedWheelTimer.class); + + private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); + private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean(); + private static final int INSTANCE_COUNT_LIMIT = 64; + private static final AtomicIntegerFieldUpdater WORKER_STATE_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState"); + + private final Worker worker = new Worker(); + private final Thread workerThread; + + private static final int WORKER_STATE_INIT = 0; + private static final int WORKER_STATE_STARTED = 1; + private static final int WORKER_STATE_SHUTDOWN = 2; + + /** + * 0 - init, 1 - started, 2 - shut down + */ + @SuppressWarnings({"unused", "FieldMayBeFinal"}) + private volatile int workerState; + + private final long tickDuration; + private final HashedWheelBucket[] wheel; + private final int mask; + private final CountDownLatch startTimeInitialized = new CountDownLatch(1); + private final Queue timeouts = new ArrayBlockingQueue(1024); + private final Queue cancelledTimeouts = new ArrayBlockingQueue(1024); + private final AtomicLong pendingTimeouts = new AtomicLong(0); + private final long maxPendingTimeouts; + + private volatile long startTime; + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}), default tick duration, and + * default number of ticks per wheel. + */ + public HashedWheelTimer() { + this(Executors.defaultThreadFactory()); + } + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}) and default number of ticks + * per wheel. + * + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @throws NullPointerException if {@code unit} is {@code null} + * @throws IllegalArgumentException if {@code tickDuration} is <= 0 + */ + public HashedWheelTimer(long tickDuration, TimeUnit unit) { + this(Executors.defaultThreadFactory(), tickDuration, unit); + } + + /** + * Creates a new timer with the default thread factory + * ({@link Executors#defaultThreadFactory()}). + * + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @throws NullPointerException if {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer(long tickDuration, TimeUnit unit, int ticksPerWheel) { + this(Executors.defaultThreadFactory(), tickDuration, unit, ticksPerWheel); + } + + /** + * Creates a new timer with the default tick duration and default number of + * ticks per wheel. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @throws NullPointerException if {@code threadFactory} is {@code null} + */ + public HashedWheelTimer(ThreadFactory threadFactory) { + this(threadFactory, 100, TimeUnit.MILLISECONDS); + } + + /** + * Creates a new timer with the default number of ticks per wheel. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if {@code tickDuration} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, long tickDuration, TimeUnit unit) { + this(threadFactory, tickDuration, unit, 512); + } + + /** + * Creates a new timer. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel) { + this(threadFactory, tickDuration, unit, ticksPerWheel, -1); + } + + /** + * Creates a new timer. + * + * @param threadFactory a {@link ThreadFactory} that creates a + * background {@link Thread} which is dedicated to + * {@link TimerTask} execution. + * @param tickDuration the duration between tick + * @param unit the time unit of the {@code tickDuration} + * @param ticksPerWheel the size of the wheel + * @param maxPendingTimeouts The maximum number of pending timeouts after which call to + * {@code newTimeout} will result in + * {@link java.util.concurrent.RejectedExecutionException} + * being thrown. No maximum pending timeouts limit is assumed if + * this value is 0 or negative. + * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code null} + * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is <= 0 + */ + public HashedWheelTimer( + ThreadFactory threadFactory, + long tickDuration, TimeUnit unit, int ticksPerWheel, + long maxPendingTimeouts) { + + if (threadFactory == null) { + throw new NullPointerException("threadFactory"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + if (tickDuration <= 0) { + throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration); + } + if (ticksPerWheel <= 0) { + throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); + } + + // Normalize ticksPerWheel to power of two and initialize the wheel. + wheel = createWheel(ticksPerWheel); + mask = wheel.length - 1; + + // Convert tickDuration to nanos. + this.tickDuration = unit.toNanos(tickDuration); + + // Prevent overflow. + if (this.tickDuration >= Long.MAX_VALUE / wheel.length) { + throw new IllegalArgumentException(String.format( + "tickDuration: %d (expected: 0 < tickDuration in nanos < %d", + tickDuration, Long.MAX_VALUE / wheel.length)); + } + workerThread = threadFactory.newThread(worker); + + this.maxPendingTimeouts = maxPendingTimeouts; + + if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT && + WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) { + reportTooManyInstances(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + super.finalize(); + } finally { + // This object is going to be GCed and it is assumed the ship has sailed to do a proper shutdown. If + // we have not yet shutdown then we want to make sure we decrement the active instance count. + if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { + INSTANCE_COUNTER.decrementAndGet(); + } + } + } + + private static HashedWheelBucket[] createWheel(int ticksPerWheel) { + if (ticksPerWheel <= 0) { + throw new IllegalArgumentException( + "ticksPerWheel must be greater than 0: " + ticksPerWheel); + } + if (ticksPerWheel > 1073741824) { + throw new IllegalArgumentException( + "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel); + } + + ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel); + HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel]; + for (int i = 0; i < wheel.length; i++) { + wheel[i] = new HashedWheelBucket(); + } + return wheel; + } + + private static int normalizeTicksPerWheel(int ticksPerWheel) { + int normalizedTicksPerWheel = 1; + while (normalizedTicksPerWheel < ticksPerWheel) { + normalizedTicksPerWheel <<= 1; + } + return normalizedTicksPerWheel; + } + + /** + * Starts the background thread explicitly. The background thread will + * start automatically on demand even if you did not call this method. + * + * @throws IllegalStateException if this timer has been + * {@linkplain #stop() stopped} already + */ + public void start() { + switch (WORKER_STATE_UPDATER.get(this)) { + case WORKER_STATE_INIT: + if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) { + workerThread.start(); + } + break; + case WORKER_STATE_STARTED: + break; + case WORKER_STATE_SHUTDOWN: + throw new IllegalStateException("cannot be started once stopped"); + default: + throw new Error("Invalid WorkerState"); + } + + // Wait until the startTime is initialized by the worker. + while (startTime == 0) { + try { + startTimeInitialized.await(); + } catch (InterruptedException ignore) { + // Ignore - it will be ready very soon. + } + } + } + + @Override + public Set stop() { + if (Thread.currentThread() == workerThread) { + throw new IllegalStateException( + HashedWheelTimer.class.getSimpleName() + + ".stop() cannot be called from " + + TimerTask.class.getSimpleName()); + } + + if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) { + // workerState can be 0 or 2 at this moment - let it always be 2. + if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { + INSTANCE_COUNTER.decrementAndGet(); + } + + return Collections.emptySet(); + } + + try { + boolean interrupted = false; + while (workerThread.isAlive()) { + workerThread.interrupt(); + try { + workerThread.join(100); + } catch (InterruptedException ignored) { + interrupted = true; + } + } + + if (interrupted) { + Thread.currentThread().interrupt(); + } + } finally { + INSTANCE_COUNTER.decrementAndGet(); + } + return worker.unprocessedTimeouts(); + } + + @Override + public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { + if (task == null) { + throw new NullPointerException("task"); + } + if (unit == null) { + throw new NullPointerException("unit"); + } + + long pendingTimeoutsCount = pendingTimeouts.incrementAndGet(); + + if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) { + pendingTimeouts.decrementAndGet(); + throw new RejectedExecutionException("Number of pending timeouts (" + + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending " + + "timeouts (" + maxPendingTimeouts + ")"); + } + + start(); + + // Add the timeout to the timeout queue which will be processed on the next tick. + // During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket. + long deadline = System.nanoTime() + unit.toNanos(delay) - startTime; + + // Guard against overflow. + if (delay > 0 && deadline < 0) { + deadline = Long.MAX_VALUE; + } + HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline); + timeouts.add(timeout); + return timeout; + } + + /** + * Returns the number of pending timeouts of this {@link Timer}. + */ + public long pendingTimeouts() { + return pendingTimeouts.get(); + } + + private static void reportTooManyInstances() { + String resourceType = StringUtils.simpleClassName(HashedWheelTimer.class); + logger.error("You are creating too many " + resourceType + " instances. " + + resourceType + " is a shared resource that must be reused across the JVM," + + "so that only a few instances are created."); + } + + private final class Worker implements Runnable { + private final Set unprocessedTimeouts = new HashSet(); + + private long tick; + + @Override + public void run() { + // Initialize the startTime. + startTime = System.nanoTime(); + if (startTime == 0) { + // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized. + startTime = 1; + } + + // Notify the other threads waiting for the initialization at start(). + startTimeInitialized.countDown(); + + do { + final long deadline = waitForNextTick(); + if (deadline > 0) { + int idx = (int) (tick & mask); + processCancelledTasks(); + HashedWheelBucket bucket = + wheel[idx]; + transferTimeoutsToBuckets(); + bucket.expireTimeouts(deadline); + tick++; + } + } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); + + // Fill the unprocessedTimeouts so we can return them from stop() method. + for (HashedWheelBucket bucket : wheel) { + bucket.clearTimeouts(unprocessedTimeouts); + } + for (; ; ) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + break; + } + if (!timeout.isCancelled()) { + unprocessedTimeouts.add(timeout); + } + } + processCancelledTasks(); + } + + private void transferTimeoutsToBuckets() { + // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just + // adds new timeouts in a loop. + for (int i = 0; i < 100000; i++) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + // all processed + break; + } + if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) { + // Was cancelled in the meantime. + continue; + } + + long calculated = timeout.deadline / tickDuration; + timeout.remainingRounds = (calculated - tick) / wheel.length; + + // Ensure we don't schedule for past. + final long ticks = Math.max(calculated, tick); + int stopIndex = (int) (ticks & mask); + + HashedWheelBucket bucket = wheel[stopIndex]; + bucket.addTimeout(timeout); + } + } + + private void processCancelledTasks() { + for (; ; ) { + HashedWheelTimeout timeout = cancelledTimeouts.poll(); + if (timeout == null) { + // all processed + break; + } + try { + timeout.remove(); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown while process a cancellation task", t); + } + } + } + } + + /** + * calculate goal nanoTime from startTime and current tick number, + * then wait until that goal has been reached. + * + * @return Long.MIN_VALUE if received a shutdown request, + * current time otherwise (with Long.MIN_VALUE changed by +1) + */ + private long waitForNextTick() { + long deadline = tickDuration * (tick + 1); + + for (; ; ) { + final long currentTime = System.nanoTime() - startTime; + long sleepTimeMs = (deadline - currentTime + 999999) / 1000000; + + if (sleepTimeMs <= 0) { + if (currentTime == Long.MIN_VALUE) { + return -Long.MAX_VALUE; + } else { + return currentTime; + } + } + if (isWindows()) { + sleepTimeMs = sleepTimeMs / 10 * 10; + } + + try { + Thread.sleep(sleepTimeMs); + } catch (InterruptedException ignored) { + if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) { + return Long.MIN_VALUE; + } + } + } + } + + Set unprocessedTimeouts() { + return Collections.unmodifiableSet(unprocessedTimeouts); + } + } + + private static final class HashedWheelTimeout implements Timeout { + + private static final int ST_INIT = 0; + private static final int ST_CANCELLED = 1; + private static final int ST_EXPIRED = 2; + private static final AtomicIntegerFieldUpdater STATE_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state"); + + private final HashedWheelTimer timer; + private final TimerTask task; + private final long deadline; + + @SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization"}) + private volatile int state = ST_INIT; + + /** + * RemainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the + * HashedWheelTimeout will be added to the correct HashedWheelBucket. + */ + long remainingRounds; + + /** + * This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list. + * As only the workerThread will act on it there is no need for synchronization / volatile. + */ + HashedWheelTimeout next; + HashedWheelTimeout prev; + + /** + * The bucket to which the timeout was added + */ + HashedWheelBucket bucket; + + HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) { + this.timer = timer; + this.task = task; + this.deadline = deadline; + } + + @Override + public Timer timer() { + return timer; + } + + @Override + public TimerTask task() { + return task; + } + + @Override + public boolean cancel() { + // only update the state it will be removed from HashedWheelBucket on next tick. + if (!compareAndSetState(ST_INIT, ST_CANCELLED)) { + return false; + } + // If a task should be canceled we put this to another queue which will be processed on each tick. + // So this means that we will have a GC latency of max. 1 tick duration which is good enough. This way + // we can make again use of our MpscLinkedQueue and so minimize the locking / overhead as much as possible. + timer.cancelledTimeouts.add(this); + return true; + } + + void remove() { + HashedWheelBucket bucket = this.bucket; + if (bucket != null) { + bucket.remove(this); + } else { + timer.pendingTimeouts.decrementAndGet(); + } + } + + public boolean compareAndSetState(int expected, int state) { + return STATE_UPDATER.compareAndSet(this, expected, state); + } + + public int state() { + return state; + } + + @Override + public boolean isCancelled() { + return state() == ST_CANCELLED; + } + + @Override + public boolean isExpired() { + return state() == ST_EXPIRED; + } + + public void expire() { + if (!compareAndSetState(ST_INIT, ST_EXPIRED)) { + return; + } + + try { + task.run(this); + } catch (Throwable t) { + if (logger.isWarnEnabled()) { + logger.warn("An exception was thrown by " + TimerTask.class.getSimpleName() + '.', t); + } + } + } + + @Override + public String toString() { + final long currentTime = System.nanoTime(); + long remaining = deadline - currentTime + timer.startTime; + String simpleClassName = StringUtils.simpleClassName(this.getClass()); + + StringBuilder buf = new StringBuilder(192) + .append(simpleClassName) + .append('(') + .append("deadline: "); + if (remaining > 0) { + buf.append(remaining) + .append(" ns later"); + } else if (remaining < 0) { + buf.append(-remaining) + .append(" ns ago"); + } else { + buf.append("now"); + } + + if (isCancelled()) { + buf.append(", cancelled"); + } + + return buf.append(", task: ") + .append(task()) + .append(')') + .toString(); + } + } + + /** + * Bucket that stores HashedWheelTimeouts. These are stored in a linked-list like datastructure to allow easy + * removal of HashedWheelTimeouts in the middle. Also the HashedWheelTimeout act as nodes themself and so no + * extra object creation is needed. + */ + private static final class HashedWheelBucket { + + /** + * Used for the linked-list datastructure + */ + private HashedWheelTimeout head; + private HashedWheelTimeout tail; + + /** + * Add {@link HashedWheelTimeout} to this bucket. + */ + void addTimeout(HashedWheelTimeout timeout) { + assert timeout.bucket == null; + timeout.bucket = this; + if (head == null) { + head = tail = timeout; + } else { + tail.next = timeout; + timeout.prev = tail; + tail = timeout; + } + } + + /** + * Expire all {@link HashedWheelTimeout}s for the given {@code deadline}. + */ + void expireTimeouts(long deadline) { + HashedWheelTimeout timeout = head; + + // process all timeouts + while (timeout != null) { + HashedWheelTimeout next = timeout.next; + if (timeout.remainingRounds <= 0) { + next = remove(timeout); + if (timeout.deadline <= deadline) { + timeout.expire(); + } else { + // The timeout was placed into a wrong slot. This should never happen. + throw new IllegalStateException(String.format( + "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline)); + } + } else if (timeout.isCancelled()) { + next = remove(timeout); + } else { + timeout.remainingRounds--; + } + timeout = next; + } + } + + public HashedWheelTimeout remove(HashedWheelTimeout timeout) { + HashedWheelTimeout next = timeout.next; + // remove timeout that was either processed or cancelled by updating the linked-list + if (timeout.prev != null) { + timeout.prev.next = next; + } + if (timeout.next != null) { + timeout.next.prev = timeout.prev; + } + + if (timeout == head) { + // if timeout is also the tail we need to adjust the entry too + if (timeout == tail) { + tail = null; + head = null; + } else { + head = next; + } + } else if (timeout == tail) { + // if the timeout is the tail modify the tail to be the prev node. + tail = timeout.prev; + } + // null out prev, next and bucket to allow for GC. + timeout.prev = null; + timeout.next = null; + timeout.bucket = null; + timeout.timer.pendingTimeouts.decrementAndGet(); + return next; + } + + /** + * Clear this bucket and return all not expired / cancelled {@link Timeout}s. + */ + void clearTimeouts(Set set) { + for (; ; ) { + HashedWheelTimeout timeout = pollTimeout(); + if (timeout == null) { + return; + } + if (timeout.isExpired() || timeout.isCancelled()) { + continue; + } + set.add(timeout); + } + } + + private HashedWheelTimeout pollTimeout() { + HashedWheelTimeout head = this.head; + if (head == null) { + return null; + } + HashedWheelTimeout next = head.next; + if (next == null) { + tail = this.head = null; + } else { + this.head = next; + next.prev = null; + } + + // null out prev and next to allow for GC. + head.next = null; + head.prev = null; + head.bucket = null; + return head; + } + } + + private boolean isWindows() { + return System.getProperty("os.name", "").toLowerCase(Locale.US).contains("win"); + } +} \ No newline at end of file diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timeout.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timeout.java new file mode 100644 index 00000000000..3d4c2318f2f --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timeout.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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.dubbo.common.timer; + +/** + * A handle associated with a {@link TimerTask} that is returned by a + * {@link Timer}. + */ +public interface Timeout { + + /** + * Returns the {@link Timer} that created this handle. + */ + Timer timer(); + + /** + * Returns the {@link TimerTask} which is associated with this handle. + */ + TimerTask task(); + + /** + * Returns {@code true} if and only if the {@link TimerTask} associated + * with this handle has been expired. + */ + boolean isExpired(); + + /** + * Returns {@code true} if and only if the {@link TimerTask} associated + * with this handle has been cancelled. + */ + boolean isCancelled(); + + /** + * Attempts to cancel the {@link TimerTask} associated with this handle. + * If the task has been executed or cancelled already, it will return with + * no side effect. + * + * @return True if the cancellation completed successfully, otherwise false + */ + boolean cancel(); +} \ No newline at end of file diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timer.java new file mode 100644 index 00000000000..9e87059124f --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/Timer.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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.dubbo.common.timer; + +import java.util.Set; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * Schedules {@link TimerTask}s for one-time future execution in a background + * thread. + */ +public interface Timer { + + /** + * Schedules the specified {@link TimerTask} for one-time execution after + * the specified delay. + * + * @return a handle which is associated with the specified task + * @throws IllegalStateException if this timer has been {@linkplain #stop() stopped} already + * @throws RejectedExecutionException if the pending timeouts are too many and creating new timeout + * can cause instability in the system. + */ + Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); + + /** + * Releases all resources acquired by this {@link Timer} and cancels all + * tasks which were scheduled but not executed yet. + * + * @return the handles associated with the tasks which were canceled by + * this method + */ + Set stop(); +} \ No newline at end of file diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/TimerTask.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/TimerTask.java new file mode 100644 index 00000000000..ce818e1c6ce --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/TimerTask.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project 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.dubbo.common.timer; + +import java.util.concurrent.TimeUnit; + +/** + * A task which is executed after the delay specified with + * {@link Timer#newTimeout(TimerTask, long, TimeUnit)} (TimerTask, long, TimeUnit)}. + */ +public interface TimerTask { + + /** + * Executed after the delay specified with + * {@link Timer#newTimeout(TimerTask, long, TimeUnit)}. + * + * @param timeout a handle which is associated with this task + */ + void run(Timeout timeout) throws Exception; +} \ No newline at end of file diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index fed75258b68..f403f2f55c1 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -47,6 +47,8 @@ public final class StringUtils { private static final Pattern INT_PATTERN = Pattern.compile("^\\d+$"); private static final int PAD_LIMIT = 8192; + private static final char PACKAGE_SEPARATOR_CHAR = '.'; + private StringUtils() { } @@ -718,4 +720,16 @@ public static String toArgumentString(Object[] args) { } return buf.toString(); } + + public static String simpleClassName(Class clazz) { + if (clazz == null) { + throw new NullPointerException("clazz"); + } + String className = clazz.getName(); + final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + if (lastDotIdx > -1) { + return className.substring(lastDotIdx + 1); + } + return className; + } } \ No newline at end of file diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/timer/HashedWheelTimerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/timer/HashedWheelTimerTest.java new file mode 100644 index 00000000000..15bd5c52a75 --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/timer/HashedWheelTimerTest.java @@ -0,0 +1,72 @@ +/* + * 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.dubbo.common.timer; + +import org.apache.dubbo.common.utils.NamedThreadFactory; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.TimeUnit; + +public class HashedWheelTimerTest { + + private class PrintTask implements TimerTask { + + @Override + public void run(Timeout timeout) { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + System.out.println("task :" + LocalDateTime.now().format(formatter)); + } + } + + @Test + public void newTimeout() throws InterruptedException { + final Timer timer = newTimer(); + for (int i = 0; i < 10; i++) { + timer.newTimeout(new PrintTask(), 1, TimeUnit.SECONDS); + Thread.sleep(1000); + } + Thread.sleep(5000); + } + + @Test + public void stop() throws InterruptedException { + final Timer timer = newTimer(); + for (int i = 0; i < 10; i++) { + timer.newTimeout(new PrintTask(), 5, TimeUnit.SECONDS); + Thread.sleep(100); + } + //stop timer + timer.stop(); + + try { + //this will throw a exception + timer.newTimeout(new PrintTask(), 5, TimeUnit.SECONDS); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Timer newTimer() { + return new HashedWheelTimer( + new NamedThreadFactory("dubbo-future-timeout", true), + 100, + TimeUnit.MILLISECONDS); + } +} \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java index 51e10dc9b56..ae9c95bd7e5 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java @@ -19,6 +19,11 @@ import org.apache.dubbo.common.Constants; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.timer.HashedWheelTimer; +import org.apache.dubbo.common.timer.Timeout; +import org.apache.dubbo.common.timer.Timer; +import org.apache.dubbo.common.timer.TimerTask; +import org.apache.dubbo.common.utils.NamedThreadFactory; import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.RemotingException; import org.apache.dubbo.remoting.TimeoutException; @@ -47,11 +52,10 @@ public class DefaultFuture implements ResponseFuture { private static final Map FUTURES = new ConcurrentHashMap(); - static { - Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer"); - th.setDaemon(true); - th.start(); - } + public static final Timer TIME_OUT_TIMER = new HashedWheelTimer( + new NamedThreadFactory("dubbo-future-timeout", true), + 30, + TimeUnit.MILLISECONDS); // invoke id. private final long id; @@ -65,7 +69,7 @@ public class DefaultFuture implements ResponseFuture { private volatile Response response; private volatile ResponseCallback callback; - public DefaultFuture(Channel channel, Request request, int timeout) { + private DefaultFuture(Channel channel, Request request, int timeout) { this.channel = channel; this.request = request; this.id = request.getId(); @@ -75,6 +79,20 @@ public DefaultFuture(Channel channel, Request request, int timeout) { CHANNELS.put(id, channel); } + /** + * check time out of the future + */ + private static void timeoutCheck(DefaultFuture future) { + TimeoutCheckTask task = new TimeoutCheckTask(future); + TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS); + } + + public static DefaultFuture newFuture(Channel channel, Request request, int timeout) { + final DefaultFuture defaultFuture = new DefaultFuture(channel, request, timeout); + timeoutCheck(defaultFuture); + return defaultFuture; + } + public static DefaultFuture getFuture(long id) { return FUTURES.get(id); } @@ -174,6 +192,29 @@ public void setCallback(ResponseCallback callback) { } } + private static class TimeoutCheckTask implements TimerTask { + + private DefaultFuture future; + + TimeoutCheckTask(DefaultFuture future) { + this.future = future; + } + + @Override + public void run(Timeout timeout) { + if (future == null || future.isDone()) { + return; + } + // create exception response. + Response timeoutResponse = new Response(future.getId()); + // set timeout status. + timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT); + timeoutResponse.setErrorMessage(future.getTimeoutMessage(true)); + // handle response. + DefaultFuture.received(future.getChannel(), timeoutResponse); + } + } + private void invokeCallback(ResponseCallback c) { ResponseCallback callbackCopy = c; if (callbackCopy == null) { @@ -277,33 +318,4 @@ private String getTimeoutMessage(boolean scan) { + timeout + " ms, request: " + request + ", channel: " + channel.getLocalAddress() + " -> " + channel.getRemoteAddress(); } - - private static class RemotingInvocationTimeoutScan implements Runnable { - - @Override - public void run() { - while (true) { - try { - for (DefaultFuture future : FUTURES.values()) { - if (future == null || future.isDone()) { - continue; - } - if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) { - // create exception response. - Response timeoutResponse = new Response(future.getId()); - // set timeout status. - timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT); - timeoutResponse.setErrorMessage(future.getTimeoutMessage(true)); - // handle response. - DefaultFuture.received(future.getChannel(), timeoutResponse); - } - } - Thread.sleep(30); - } catch (Throwable e) { - logger.error("Exception when scan the timeout invocation of remoting.", e); - } - } - } - } - } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannel.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannel.java index 34cef2bc4e8..73503cd4c2d 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannel.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannel.java @@ -111,7 +111,7 @@ public ResponseFuture request(Object request, int timeout) throws RemotingExcept req.setVersion(Version.getProtocolVersion()); req.setTwoWay(true); req.setData(request); - DefaultFuture future = new DefaultFuture(channel, req, timeout); + DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout); try { channel.send(req); } catch (RemotingException e) { diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/DefaultFutureTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/DefaultFutureTest.java new file mode 100644 index 00000000000..d088a56f9f4 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/DefaultFutureTest.java @@ -0,0 +1,124 @@ +/* + * 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.dubbo.remoting.exchange.support; + +import org.apache.dubbo.remoting.Channel; +import org.apache.dubbo.remoting.TimeoutException; +import org.apache.dubbo.remoting.exchange.Request; +import org.apache.dubbo.remoting.handler.MockedChannel; +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.atomic.AtomicInteger; + +public class DefaultFutureTest { + + private static final AtomicInteger index = new AtomicInteger(); + + @Test + public void newFuture() { + DefaultFuture future = defaultFuture(3000); + Assert.assertNotNull("new future return null", future); + } + + @Test + public void isDone() { + DefaultFuture future = defaultFuture(3000); + Assert.assertTrue("init future is finished!", !future.isDone()); + + //cancel a future + future.cancel(); + Assert.assertTrue("cancel a future failed!", future.isDone()); + } + + /** + * for example, it will print like this: + * before a future is create , time is : 2018-06-21 15:06:17 + * after a future is timeout , time is : 2018-06-21 15:06:22 + *

+ * The exception info print like: + * Sending request timeout in client-side by scan timer. + * start time: 2018-06-21 15:13:02.215, end time: 2018-06-21 15:13:07.231... + */ + @Test + public void timeoutNotSend() throws Exception { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + System.out.println("before a future is create , time is : " + LocalDateTime.now().format(formatter)); + // timeout after 5 seconds. + DefaultFuture f = defaultFuture(5000); + while (!f.isDone()) { + //spin + Thread.sleep(100); + } + System.out.println("after a future is timeout , time is : " + LocalDateTime.now().format(formatter)); + + // get operate will throw a timeout exception, because the future is timeout. + try { + f.get(); + } catch (Exception e) { + Assert.assertTrue("catch exception is not timeout exception!", e instanceof TimeoutException); + System.out.println(e.getMessage()); + } + } + + /** + * for example, it will print like this: + * before a future is create , time is : 2018-06-21 15:11:31 + * after a future is timeout , time is : 2018-06-21 15:11:36 + *

+ * The exception info print like: + * Waiting server-side response timeout by scan timer. + * start time: 2018-06-21 15:12:38.337, end time: 2018-06-21 15:12:43.354... + */ + @Test + public void timeoutSend() throws Exception { + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + System.out.println("before a future is create , time is : " + LocalDateTime.now().format(formatter)); + // timeout after 5 seconds. + Channel channel = new MockedChannel(); + Request request = new Request(10); + DefaultFuture f = DefaultFuture.newFuture(channel, request, 5000); + //mark the future is sent + DefaultFuture.sent(channel, request); + while (!f.isDone()) { + //spin + Thread.sleep(100); + } + System.out.println("after a future is timeout , time is : " + LocalDateTime.now().format(formatter)); + + // get operate will throw a timeout exception, because the future is timeout. + try { + f.get(); + } catch (Exception e) { + Assert.assertTrue("catch exception is not timeout exception!", e instanceof TimeoutException); + System.out.println(e.getMessage()); + } + } + + /** + * mock a default future + */ + private DefaultFuture defaultFuture(int timeout) { + Channel channel = new MockedChannel(); + Request request = new Request(index.getAndIncrement()); + return DefaultFuture.newFuture(channel, request, timeout); + } + +} \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java index f5222c64579..0a372728c11 100644 --- a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java +++ b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java @@ -130,7 +130,7 @@ public void testDecodeReplyResponse() throws Exception { Request request = createRequest(); - DefaultFuture future = new DefaultFuture(channel, request, 10); + DefaultFuture future = DefaultFuture.newFuture(channel, request, 10); TMessage message = new TMessage("echoString", TMessageType.REPLY, ThriftCodec.getSeqId()); @@ -205,7 +205,7 @@ public void testDecodeExceptionResponse() throws Exception { Request request = createRequest(); - DefaultFuture future = new DefaultFuture(channel, request, 10); + DefaultFuture future = DefaultFuture.newFuture(channel, request, 10); TMessage message = new TMessage("echoString", TMessageType.EXCEPTION, ThriftCodec.getSeqId()); From 7500033b9d22e2162c5ab614d3233eb43aaeebd5 Mon Sep 17 00:00:00 2001 From: Ian Luo Date: Wed, 22 Aug 2018 16:12:13 +0800 Subject: [PATCH 02/12] supplemental change for pull request 1973 (#2329) * supplemental change for pull request 1973 * update LICENSE, to put all netty related together --- LICENSE | 6 +++++- .../dubbo/common/timer/HashedWheelTimer.java | 8 ++++---- .../apache/dubbo/common/utils/ClassHelper.java | 16 +++++++++++++++- .../apache/dubbo/common/utils/StringUtils.java | 16 +--------------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/LICENSE b/LICENSE index 42e2705ba00..b85622380cf 100644 --- a/LICENSE +++ b/LICENSE @@ -217,11 +217,15 @@ This product bundles and repackages the following code in Google Guava 16.0.1, w * com.google.common.util.concurrent.ListenableFuture * com.google.common.util.concurrent.ListenableFutureTask -For the package org.apache.dubbo.common.threadlocal: +For the package org.apache.dubbo.common.threadlocal and org.apache.dubbo.common.timer: This product contains a modified portion of 'Netty', an event-driven asynchronous network application framework also under a "Apache License 2.0" license, see https://github.com/netty/netty/blob/4.1/LICENSE.txt: * io.netty.util.concurrent.FastThreadLocal * io.netty.util.internal.InternalThreadLocalMap + * io.netty.util.Timer + * io.netty.util.TimerTask + * io.netty.util.Timeout + * io.netty.util.HashedWheelTimer diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java index db5c43fee97..a54cc70b9b4 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/timer/HashedWheelTimer.java @@ -18,7 +18,7 @@ import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.common.utils.ClassHelper; import java.util.Queue; import java.util.Set; @@ -408,7 +408,7 @@ public long pendingTimeouts() { } private static void reportTooManyInstances() { - String resourceType = StringUtils.simpleClassName(HashedWheelTimer.class); + String resourceType = ClassHelper.simpleClassName(HashedWheelTimer.class); logger.error("You are creating too many " + resourceType + " instances. " + resourceType + " is a shared resource that must be reused across the JVM," + "so that only a few instances are created."); @@ -650,7 +650,7 @@ public void expire() { public String toString() { final long currentTime = System.nanoTime(); long remaining = deadline - currentTime + timer.startTime; - String simpleClassName = StringUtils.simpleClassName(this.getClass()); + String simpleClassName = ClassHelper.simpleClassName(this.getClass()); StringBuilder buf = new StringBuilder(192) .append(simpleClassName) @@ -802,4 +802,4 @@ private HashedWheelTimeout pollTimeout() { private boolean isWindows() { return System.getProperty("os.name", "").toLowerCase(Locale.US).contains("win"); } -} \ No newline at end of file +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassHelper.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassHelper.java index 8ecb8f4045a..e38752fcabf 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassHelper.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ClassHelper.java @@ -45,6 +45,8 @@ public class ClassHelper { */ private static final Map, Class> primitiveWrapperTypeMap = new HashMap, Class>(8); + private static final char PACKAGE_SEPARATOR_CHAR = '.'; + static { primitiveWrapperTypeMap.put(Boolean.class, boolean.class); primitiveWrapperTypeMap.put(Byte.class, byte.class); @@ -205,4 +207,16 @@ public static String toShortString(Object obj) { return obj.getClass().getSimpleName() + "@" + System.identityHashCode(obj); } -} \ No newline at end of file + + public static String simpleClassName(Class clazz) { + if (clazz == null) { + throw new NullPointerException("clazz"); + } + String className = clazz.getName(); + final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); + if (lastDotIdx > -1) { + return className.substring(lastDotIdx + 1); + } + return className; + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index f403f2f55c1..ccc5a10ccc8 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -47,8 +47,6 @@ public final class StringUtils { private static final Pattern INT_PATTERN = Pattern.compile("^\\d+$"); private static final int PAD_LIMIT = 8192; - private static final char PACKAGE_SEPARATOR_CHAR = '.'; - private StringUtils() { } @@ -720,16 +718,4 @@ public static String toArgumentString(Object[] args) { } return buf.toString(); } - - public static String simpleClassName(Class clazz) { - if (clazz == null) { - throw new NullPointerException("clazz"); - } - String className = clazz.getName(); - final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); - if (lastDotIdx > -1) { - return className.substring(lastDotIdx + 1); - } - return className; - } -} \ No newline at end of file +} From fcd1af81fd2fd6ad55c78f281d7b29feb10a006a Mon Sep 17 00:00:00 2001 From: xujingfeng <250577914@qq.com> Date: Wed, 22 Aug 2018 16:17:44 +0800 Subject: [PATCH 03/12] [Dubbo- support tag router feature] Add a new Router implement -- TagRouter (#2228) * tagRouter feature * update dubbo.xsd * remove reference router param * add Unit Test * rollback pom.xml for merge * rollback pom.xml for merge * fix checkstyle * fix checkstyle * fix unit test * format import style * add license&remove author info * trigger again --- .../rpc/cluster/router/tag/TagRouter.java | 109 +++++++++++ .../cluster/router/tag/TagRouterFactory.java | 32 ++++ ...org.apache.dubbo.rpc.cluster.RouterFactory | 3 +- .../rpc/cluster/router/tag/TagRouterTest.java | 169 ++++++++++++++++++ .../org/apache/dubbo/common/Constants.java | 4 + .../dubbo/config/AbstractInterfaceConfig.java | 1 - .../dubbo/config/AbstractReferenceConfig.java | 1 - .../dubbo/config/AbstractServiceConfig.java | 10 ++ .../dubbo/config/annotation/Service.java | 5 + .../dubbo/config/spring/AnnotationBean.java | 24 +-- .../main/resources/META-INF/compat/dubbo.xsd | 6 + .../src/main/resources/META-INF/dubbo.xsd | 6 + .../integration/RegistryDirectory.java | 8 +- 13 files changed, 360 insertions(+), 18 deletions(-) create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java create mode 100644 dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java create mode 100644 dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java new file mode 100644 index 00000000000..38fd6166eff --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouter.java @@ -0,0 +1,109 @@ +/* + * 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.dubbo.rpc.cluster.router.tag; + + +import org.apache.dubbo.common.Constants; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.cluster.Router; + +import java.util.ArrayList; +import java.util.List; + +/** + * TagRouter + */ +public class TagRouter implements Router, Comparable { + + private static final Logger logger = LoggerFactory.getLogger(TagRouter.class); + + private final int priority; + private final URL url; + + public static final URL ROUTER_URL = new URL("tag", Constants.ANYHOST_VALUE, 0, Constants.ANY_VALUE).addParameters(Constants.RUNTIME_KEY, "true"); + + public TagRouter(URL url) { + this.url = url; + this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); + } + + public TagRouter() { + this.url = ROUTER_URL; + this.priority = url.getParameter(Constants.PRIORITY_KEY, 0); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public List> route(List> invokers, URL url, Invocation invocation) throws RpcException { + // filter + List> result = new ArrayList<>(); + try { + // Dynamic param + String tag = RpcContext.getContext().getAttachment(Constants.REQUEST_TAG_KEY); + // Tag request + if (!StringUtils.isEmpty(tag)) { + // Select tag invokers first + for (Invoker invoker : invokers) { + if (tag.equals(invoker.getUrl().getParameter(Constants.TAG_KEY))) { + result.add(invoker); + } + } + // If no invoker be selected, downgrade to normal invokers + if (result.isEmpty()) { + for (Invoker invoker : invokers) { + if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) { + result.add(invoker); + } + } + } + // Normal request + } else { + for (Invoker invoker : invokers) { + // Can't access tag invoker,only normal invoker should be selected + if (StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY))) { + result.add(invoker); + } + } + } + return result; + } catch (Exception e) { + logger.error("Route by tag error,return all invokers.", e); + } + // Downgrade to all invokers + return invokers; + } + + @Override + public int compareTo(Router o) { + if (o == null || o.getClass() != TagRouter.class) { + return 1; + } + TagRouter c = (TagRouter) o; + return this.priority == c.priority ? url.toFullString().compareTo(c.url.toFullString()) : (this.priority > c.priority ? 1 : -1); + } +} diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java new file mode 100644 index 00000000000..05ad427ce6e --- /dev/null +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterFactory.java @@ -0,0 +1,32 @@ +/* + * 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.dubbo.rpc.cluster.router.tag; + + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.cluster.Router; +import org.apache.dubbo.rpc.cluster.RouterFactory; + +public class TagRouterFactory implements RouterFactory { + + public static final String NAME = "tag"; + + @Override + public Router getRouter(URL url) { + return new TagRouter(url); + } +} diff --git a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory index 0ada9c3be9c..2d4717cfaa3 100644 --- a/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory +++ b/dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.RouterFactory @@ -1,3 +1,4 @@ file=org.apache.dubbo.rpc.cluster.router.file.FileRouterFactory script=org.apache.dubbo.rpc.cluster.router.script.ScriptRouterFactory -condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory \ No newline at end of file +condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory +tag=org.apache.dubbo.rpc.cluster.router.tag.TagRouterFactory \ No newline at end of file diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java new file mode 100644 index 00000000000..839af44efe9 --- /dev/null +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/router/tag/TagRouterTest.java @@ -0,0 +1,169 @@ +/* + * 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.dubbo.rpc.cluster.router.tag; + +import org.apache.dubbo.common.Constants; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.cluster.Router; +import org.apache.dubbo.rpc.cluster.RouterFactory; +import org.apache.dubbo.rpc.cluster.router.MockInvoker; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TagRouterTest { + + private URL tagUrl = new URL("tag" + , Constants.ANYHOST_VALUE, 0 + , Constants.ANY_VALUE) + .addParameters( + Constants.RUNTIME_KEY, "true" + ); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + } + + @Test + public void testRoute_matchTag() { + + RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "red"); + + List> invokers = new ArrayList<>(); + Invoker redInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red")); + Invoker yellowInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow")); + Invoker blueInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue")); + Invoker defaultInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.4:20880/com.foo.BarService")); + + invokers.add(redInvoker); + invokers.add(yellowInvoker); + invokers.add(blueInvoker); + invokers.add(defaultInvoker); + + Router tagRouter = new TagRouterFactory().getRouter(tagUrl); + List> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation()); + Assert.assertTrue(filteredInvokers.contains(redInvoker)); + Assert.assertFalse(filteredInvokers.contains(yellowInvoker)); + Assert.assertFalse(filteredInvokers.contains(blueInvoker)); + Assert.assertFalse(filteredInvokers.contains(defaultInvoker)); + } + + @Test + public void testRoute_matchDefault() { + + RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, ""); + + List> invokers = new ArrayList<>(); + Invoker redInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red")); + Invoker yellowInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow")); + Invoker blueInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue")); + Invoker defaultInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.4:20880/com.foo.BarService")); + + invokers.add(redInvoker); + invokers.add(yellowInvoker); + invokers.add(blueInvoker); + invokers.add(defaultInvoker); + + Router tagRouter = new TagRouterFactory().getRouter(tagUrl); + List> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation()); + Assert.assertTrue(filteredInvokers.contains(defaultInvoker)); + Assert.assertFalse(filteredInvokers.contains(yellowInvoker)); + Assert.assertFalse(filteredInvokers.contains(blueInvoker)); + Assert.assertFalse(filteredInvokers.contains(redInvoker)); + } + + @Test + public void testRoute_requestWithTag_shouldDowngrade() { + + RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, "black"); + + List> invokers = new ArrayList<>(); + Invoker redInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red")); + Invoker yellowInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow")); + Invoker blueInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue")); + Invoker defaultInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.4:20880/com.foo.BarService")); + + invokers.add(redInvoker); + invokers.add(yellowInvoker); + invokers.add(blueInvoker); + invokers.add(defaultInvoker); + + Router tagRouter = new TagRouterFactory().getRouter(tagUrl); + List> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation()); + Assert.assertTrue(filteredInvokers.contains(defaultInvoker)); + Assert.assertFalse(filteredInvokers.contains(yellowInvoker)); + Assert.assertFalse(filteredInvokers.contains(blueInvoker)); + Assert.assertFalse(filteredInvokers.contains(redInvoker)); + } + + @Test + public void testRoute_requestWithoutTag_shouldNotDowngrade() { + + RpcContext.getContext().setAttachment(Constants.REQUEST_TAG_KEY, ""); + + List> invokers = new ArrayList<>(); + Invoker redInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.1:20880/com.foo.BarService?tag=red")); + Invoker yellowInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.2:20880/com.foo.BarService?tag=yellow")); + Invoker blueInvoker = new MockInvoker<>(URL.valueOf( + "dubbo://10.20.3.3:20880/com.foo.BarService?tag=blue")); + + invokers.add(redInvoker); + invokers.add(yellowInvoker); + invokers.add(blueInvoker); + + Router tagRouter = new TagRouterFactory().getRouter(tagUrl); + List> filteredInvokers = tagRouter.route(invokers, URL.valueOf("consumer://" + NetUtils.getLocalHost() + "/com.foo.BarService"), new RpcInvocation()); + Assert.assertEquals(0, filteredInvokers.size()); + } + + @Test + public void testRoute_createBySpi() { + URL zkProvider = URL.valueOf("zookeeper://10.20.3.1:20880/com.foo.BarService?router=tag"); + String parameter = zkProvider.getParameter(Constants.ROUTER_KEY); + RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(parameter); + Router tagRouter = routerFactory.getRouter(zkProvider); + Assert.assertTrue(tagRouter instanceof TagRouter); + } + +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java index a1c8fac34c6..95069306907 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/Constants.java @@ -641,6 +641,10 @@ public class Constants { public static final String MULTICAST = "multicast"; + public static final String TAG_KEY = "tag"; + + public static final String REQUEST_TAG_KEY = "request.tag"; + /* * private Constants(){ } */ diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java index 91a4a1641d6..70ab62e4493 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java @@ -522,5 +522,4 @@ public String getScope() { public void setScope(String scope) { this.scope = scope; } - } \ No newline at end of file diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java index 08f3ad5e97b..552ccf1e4a7 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractReferenceConfig.java @@ -197,5 +197,4 @@ public void setGroup(String group) { checkKey("group", group); this.group = group; } - } diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java index f23341fff17..4da1ffa89db 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/AbstractServiceConfig.java @@ -73,6 +73,9 @@ public abstract class AbstractServiceConfig extends AbstractInterfaceConfig { // serialization private String serialization; + // provider tag + protected String tag; + public String getVersion() { return version; } @@ -240,4 +243,11 @@ public void setSerialization(String serialization) { this.serialization = serialization; } + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } } diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java index bee838745e4..6f956f50c05 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/annotation/Service.java @@ -263,4 +263,9 @@ * Registry spring bean name */ String[] registry() default {}; + + /** + * Service tag name + */ + String tag() default ""; } diff --git a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java index 61513547c15..7612c3302a3 100644 --- a/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java +++ b/dubbo-config/dubbo-config-spring/src/main/java/org/apache/dubbo/config/spring/AnnotationBean.java @@ -33,7 +33,6 @@ import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.annotation.Reference; import org.apache.dubbo.config.annotation.Service; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; @@ -76,7 +75,7 @@ public String getPackage() { public void setPackage(String annotationPackage) { this.annotationPackage = annotationPackage; this.annotationPackages = (annotationPackage == null || annotationPackage.length() == 0) ? null - : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage); + : Constants.COMMA_SPLIT_PATTERN.split(annotationPackage); } @Override @@ -86,7 +85,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) - throws BeansException { + throws BeansException { if (annotationPackage == null || annotationPackage.length() == 0) { return; } @@ -135,7 +134,7 @@ public void destroy() { @Override public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { + throws BeansException { if (!isMatchPackage(bean)) { return bean; } @@ -144,7 +143,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) ServiceBean serviceConfig = new ServiceBean(service); serviceConfig.setRef(bean); if (void.class.equals(service.interfaceClass()) - && "".equals(service.interfaceName())) { + && "".equals(service.interfaceName())) { if (bean.getClass().getInterfaces().length > 0) { serviceConfig.setInterface(bean.getClass().getInterfaces()[0]); } else { @@ -186,6 +185,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) } serviceConfig.setProtocols(protocolConfigs); } + if (service.tag().length() > 0) { + serviceConfig.setTag(service.tag()); + } try { serviceConfig.afterPropertiesSet(); } catch (RuntimeException e) { @@ -202,7 +204,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) @Override public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { + throws BeansException { if (!isMatchPackage(bean)) { return bean; } @@ -210,9 +212,9 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) for (Method method : methods) { String name = method.getName(); if (name.length() > 3 && name.startsWith("set") - && method.getParameterTypes().length == 1 - && Modifier.isPublic(method.getModifiers()) - && !Modifier.isStatic(method.getModifiers())) { + && method.getParameterTypes().length == 1 + && Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers())) { try { Reference reference = method.getAnnotation(Reference.class); if (reference != null) { @@ -262,8 +264,8 @@ private Object refer(Reference reference, Class referenceClass) { //method.ge if (referenceConfig == null) { referenceConfig = new ReferenceBean(reference); if (void.class.equals(reference.interfaceClass()) - && "".equals(reference.interfaceName()) - && referenceClass.isInterface()) { + && "".equals(reference.interfaceName()) + && referenceClass.isInterface()) { referenceConfig.setInterface(referenceClass); } if (applicationContext != null) { diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd index 6206048311b..290c750c93b 100644 --- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd +++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd @@ -307,6 +307,12 @@ + + + + + + diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd index ecf7d7b7a5d..ecec23acc0a 100644 --- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd +++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/dubbo.xsd @@ -307,6 +307,12 @@ + + + + + + diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java index 727bca297e2..6b366b2921b 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java @@ -55,7 +55,6 @@ /** * RegistryDirectory - * */ public class RegistryDirectory extends AbstractDirectory implements NotifyListener { @@ -445,7 +444,8 @@ private List> route(List> invokers, String method) { List routers = getRouters(); if (routers != null) { for (Router router : routers) { - if (router.getUrl() != null) { + // If router's url not null and is not route by runtime,we filter invokers here + if (router.getUrl() != null && !router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { invokers = router.route(invokers, getConsumerUrl(), invocation); } } @@ -573,8 +573,8 @@ public List> doList(Invocation invocation) { if (forbidden) { // 1. No service provider 2. Service providers are disabled throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, - "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() - + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); + "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); } List> invokers = null; Map>> localMethodInvokerMap = this.methodInvokerMap; // local reference From 53d34f7ad191495eecdb26b34ea787516792090e Mon Sep 17 00:00:00 2001 From: Andrea Del Bene Date: Thu, 23 Aug 2018 06:37:33 +0200 Subject: [PATCH 04/12] Changed parent pom to Dubbo root (#2333) --- dubbo-bom/pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dubbo-bom/pom.xml b/dubbo-bom/pom.xml index 6c20d30374f..0bf81218d6a 100644 --- a/dubbo-bom/pom.xml +++ b/dubbo-bom/pom.xml @@ -4,9 +4,9 @@ 4.0.0 - org.apache - apache - 19 + org.apache.dubbo + dubbo-parent + 2.7.0-SNAPSHOT org.apache.dubbo @@ -313,4 +313,4 @@ - \ No newline at end of file + From c6fd6841b87cd74eb4efd82d4495b9230441935f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E6=97=A0=E4=B8=A4=E4=B8=B6?= <442367943@qq.com> Date: Thu, 23 Aug 2018 14:08:47 +0800 Subject: [PATCH 05/12] Direct return when the server goes down unnormally. (#2185) * Keep the unfinished request in every channel. When the server goes down un-normally, return the unfinished requests directly. The current way is to wait until timeout. * default method * direct return unfinished requests when remote inactive or disconnect * direct return unfinished requests when remote inactive or disconnect * name and doc * name and doc * fix unit test * fix unit test * fix unit test * close heartbeat to fix unit test * sth test * sth test * sth test * sth test * close when client close * fix unit test * fix unit test * fix unit test * ignore remote invoke unit test * don't clear request when channelInactive * replace import * * remove unuse import * optimize * optimize * fix ci failed * fix ci failed * fix ci failed * fix ci failed * fix ci failed * fix ci failed * fix ci failed * optimize unfinished request keeper * rebase onto master * fix review code --- .../org/apache/dubbo/remoting/Channel.java | 3 -- .../remoting/exchange/ExchangeChannel.java | 1 - .../dubbo/remoting/exchange/Response.java | 5 +++ .../exchange/support/DefaultFuture.java | 45 ++++++++++++++++--- .../support/header/HeaderExchangeHandler.java | 1 + .../remoting/transport/AbstractChannel.java | 1 - .../dubbo/DubboInvokerAvilableTest.java | 1 - .../rpc/protocol/dubbo/MultiThreadTest.java | 13 +++--- .../ReferenceCountExchangeClientTest.java | 8 ++++ .../rpc/protocol/dubbo/RpcFilterTest.java | 14 +++--- .../rpc/protocol/thrift/ThriftCodecTest.java | 1 - 11 files changed, 69 insertions(+), 24 deletions(-) diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Channel.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Channel.java index 8c9531f858a..aa54b7a6566 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Channel.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Channel.java @@ -21,8 +21,6 @@ /** * Channel. (API/SPI, Prototype, ThreadSafe) * - * - * * @see org.apache.dubbo.remoting.Client * @see org.apache.dubbo.remoting.Server#getChannels() * @see org.apache.dubbo.remoting.Server#getChannel(InetSocketAddress) @@ -73,5 +71,4 @@ public interface Channel extends Endpoint { * @param key key. */ void removeAttribute(String key); - } \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/ExchangeChannel.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/ExchangeChannel.java index fa6d5bf4107..3922626fdfb 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/ExchangeChannel.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/ExchangeChannel.java @@ -57,5 +57,4 @@ public interface ExchangeChannel extends Channel { */ @Override void close(int timeout); - } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/Response.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/Response.java index bfd72f5d7e6..c24e5e4d9ac 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/Response.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/Response.java @@ -40,6 +40,11 @@ public class Response { */ public static final byte SERVER_TIMEOUT = 31; + /** + * channel inactive, directly return the unfinished requests. + */ + public static final byte CHANNEL_INACTIVE = 35; + /** * request format error. */ diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java index ae9c95bd7e5..5fa6cfe9a47 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java @@ -48,9 +48,9 @@ public class DefaultFuture implements ResponseFuture { private static final Logger logger = LoggerFactory.getLogger(DefaultFuture.class); - private static final Map CHANNELS = new ConcurrentHashMap(); + private static final Map CHANNELS = new ConcurrentHashMap<>(); - private static final Map FUTURES = new ConcurrentHashMap(); + private static final Map FUTURES = new ConcurrentHashMap<>(); public static final Timer TIME_OUT_TIMER = new HashedWheelTimer( new NamedThreadFactory("dubbo-future-timeout", true), @@ -87,10 +87,21 @@ private static void timeoutCheck(DefaultFuture future) { TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS); } + /** + * init a DefaultFuture + * 1.init a DefaultFuture + * 2.timeout check + * + * @param channel channel + * @param request the request + * @param timeout timeout + * @return a new DefaultFuture + */ public static DefaultFuture newFuture(Channel channel, Request request, int timeout) { - final DefaultFuture defaultFuture = new DefaultFuture(channel, request, timeout); - timeoutCheck(defaultFuture); - return defaultFuture; + final DefaultFuture future = new DefaultFuture(channel, request, timeout); + // timeout check + timeoutCheck(future); + return future; } public static DefaultFuture getFuture(long id) { @@ -108,6 +119,29 @@ public static void sent(Channel channel, Request request) { } } + /** + * close a channel when a channel is inactive + * directly return the unfinished requests. + * + * @param channel channel to close + */ + public static void closeChannel(Channel channel) { + for (long id : CHANNELS.keySet()) { + if (channel.equals(CHANNELS.get(id))) { + DefaultFuture future = getFuture(id); + if (future != null && !future.isDone()) { + Response disconnectResponse = new Response(future.getId()); + disconnectResponse.setStatus(Response.CHANNEL_INACTIVE); + disconnectResponse.setErrorMessage("Channel " + + channel + + " is inactive. Directly return the unFinished request : " + + future.getRequest()); + DefaultFuture.received(channel, disconnectResponse); + } + } + } + } + public static void received(Channel channel, Response response) { try { DefaultFuture future = FUTURES.remove(response.getId()); @@ -212,6 +246,7 @@ public void run(Timeout timeout) { timeoutResponse.setErrorMessage(future.getTimeoutMessage(true)); // handle response. DefaultFuture.received(future.getChannel(), timeoutResponse); + } } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeHandler.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeHandler.java index 5532620d7a2..1be8827a8db 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeHandler.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeHandler.java @@ -146,6 +146,7 @@ public void disconnected(Channel channel) throws RemotingException { try { handler.disconnected(exchangeChannel); } finally { + DefaultFuture.closeChannel(channel); HeaderExchangeChannel.removeChannelIfDisconnected(channel); } } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java index 58429d0000f..bacc2646bfd 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/transport/AbstractChannel.java @@ -43,5 +43,4 @@ public void send(Object message, boolean sent) throws RemotingException { public String toString() { return getLocalAddress() + " -> " + getRemoteAddress(); } - } diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java index 892a45d7027..ced3d067751 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvokerAvilableTest.java @@ -131,7 +131,6 @@ public void test_Lazy_ChannelReadOnly() throws Exception { } //invoke method --> init client - IDemoService service = (IDemoService) proxy.getProxy(invoker); Assert.assertEquals("ok", service.get()); diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/MultiThreadTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/MultiThreadTest.java index 4e573b8fd69..ce43473025b 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/MultiThreadTest.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/MultiThreadTest.java @@ -23,30 +23,31 @@ import org.apache.dubbo.rpc.ProxyFactory; import org.apache.dubbo.rpc.protocol.dubbo.support.DemoService; import org.apache.dubbo.rpc.protocol.dubbo.support.DemoServiceImpl; - -import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -public class MultiThreadTest extends TestCase { +public class MultiThreadTest { private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + @Test public void testDubboMultiThreadInvoke() throws Exception { Exporter rpcExporter = protocol.export(proxy.getInvoker(new DemoServiceImpl(), DemoService.class, URL.valueOf("dubbo://127.0.0.1:20259/TestService"))); final AtomicInteger counter = new AtomicInteger(); final DemoService service = proxy.getProxy(protocol.refer(DemoService.class, URL.valueOf("dubbo://127.0.0.1:20259/TestService"))); - assertEquals(service.getSize(new String[]{"123", "456", "789"}), 3); + Assert.assertEquals(service.getSize(new String[]{"123", "456", "789"}), 3); final StringBuffer sb = new StringBuffer(); for (int i = 0; i < 1024 * 64 + 32; i++) sb.append('A'); - assertEquals(sb.toString(), service.echo(sb.toString())); + Assert.assertEquals(sb.toString(), service.echo(sb.toString())); ExecutorService exec = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { @@ -55,7 +56,7 @@ public void testDubboMultiThreadInvoke() throws Exception { public void run() { for (int i = 0; i < 30; i++) { System.out.println(fi + ":" + counter.getAndIncrement()); - assertEquals(service.echo(sb.toString()), sb.toString()); + Assert.assertEquals(service.echo(sb.toString()), sb.toString()); } } }); diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java index 2a315073452..ea7743ad524 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/ReferenceCountExchangeClientTest.java @@ -123,6 +123,14 @@ public void test_counter_error() { Assert.assertEquals("should not warning message", 0, LogUtil.findMessage(errorMsg)); // counter is incorrect, invocation still succeeds client.close(); + + // wait close done. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Assert.fail(); + } + Assert.assertEquals("hello", helloService.hello()); Assert.assertEquals("should warning message", 1, LogUtil.findMessage(errorMsg)); diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/RpcFilterTest.java b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/RpcFilterTest.java index c1ac717140a..f9cca922c11 100644 --- a/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/RpcFilterTest.java +++ b/dubbo-rpc/dubbo-rpc-dubbo/src/test/java/org/apache/dubbo/rpc/protocol/dubbo/RpcFilterTest.java @@ -24,23 +24,25 @@ import org.apache.dubbo.rpc.protocol.dubbo.support.DemoServiceImpl; import org.apache.dubbo.rpc.service.EchoService; -import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; -public class RpcFilterTest extends TestCase { +public class RpcFilterTest { private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); private ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); + @Test public void testRpcFilter() throws Exception { DemoService service = new DemoServiceImpl(); URL url = URL.valueOf("dubbo://127.0.0.1:9010/org.apache.dubbo.rpc.DemoService?service.filter=echo"); protocol.export(proxy.getInvoker(service, DemoService.class, url)); service = proxy.getProxy(protocol.refer(DemoService.class, url)); - assertEquals("123", service.echo("123")); + Assert.assertEquals("123", service.echo("123")); // cast to EchoService EchoService echo = proxy.getProxy(protocol.refer(EchoService.class, url)); - assertEquals(echo.$echo("test"), "test"); - assertEquals(echo.$echo("abcdefg"), "abcdefg"); - assertEquals(echo.$echo(1234), 1234); + Assert.assertEquals(echo.$echo("test"), "test"); + Assert.assertEquals(echo.$echo("abcdefg"), "abcdefg"); + Assert.assertEquals(echo.$echo(1234), 1234); } } \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java index 0a372728c11..a21e3786bd6 100644 --- a/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java +++ b/dubbo-rpc/dubbo-rpc-thrift/src/test/java/org/apache/dubbo/rpc/protocol/thrift/ThriftCodecTest.java @@ -38,7 +38,6 @@ import org.apache.thrift.transport.TIOStreamTransport; import org.apache.thrift.transport.TTransport; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import java.io.ByteArrayInputStream; From f720ccb965d490e6cc328af8d3e9820cb6eaf8f7 Mon Sep 17 00:00:00 2001 From: Jerrick Zhu Date: Thu, 23 Aug 2018 16:37:08 +0800 Subject: [PATCH 06/12] fix travis cov (#2337) --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3e5e249c7ab..fe60e30958c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,10 @@ cache: directories: - '$HOME/.m2/repository' -install: - - ./mvnw clean install -DskipTests=true -Dcheckstyle.skip=true -Drat.skip=true -Dmaven.javadoc.skip=true +install: true script: - - travis_wait 30 ./mvnw test + - travis_wait 30 ./mvnw clean package -DskipTests=false -Dcheckstyle.skip=true -Drat.skip=true -Dmaven.javadoc.skip=true after_success: - bash <(curl -s https://codecov.io/bash) From b4db875f30a624e9f8a376189075c1ee59693837 Mon Sep 17 00:00:00 2001 From: Yuhao Bi Date: Tue, 28 Aug 2018 09:52:57 +0800 Subject: [PATCH 07/12] Update several documentation links (#2354) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ffb16af5515..e5d2f68e68c 100644 --- a/README.md +++ b/README.md @@ -109,9 +109,9 @@ public class Consumer { ### Next steps -* [Dubbo user manual](http://dubbo.apache.org/#/docs/user/preface/background.md) - How to use Dubbo and all its features. -* [Dubbo developer guide](http://dubbo.apache.org/#/docs/dev/build.md) - How to invovle in Dubbo development. -* [Dubbo admin manual](http://dubbo.apache.org/#/docs/admin/install/provider-demo.md) - How to admin and manage Dubbo services. +* [Dubbo user manual](http://dubbo.apache.org/en-us/docs/user/preface/background.html) - How to use Dubbo and all its features. +* [Dubbo developer guide](http://dubbo.apache.org/en-us/docs/dev/build.html) - How to invovle in Dubbo development. +* [Dubbo admin manual](http://dubbo.apache.org/en-us/docs/admin/install/provider-demo.html) - How to admin and manage Dubbo services. ## Contact From 5f8adf0f20c2e1823be64eb0bbe42d4ca38401d7 Mon Sep 17 00:00:00 2001 From: Jerrick Zhu Date: Tue, 28 Aug 2018 15:38:00 +0800 Subject: [PATCH 08/12] Enable checkstyle and rat plugin for travis CI (#2393) --- .travis.yml | 2 +- .../apache/dubbo/rpc/cluster/RouterFactory.java | 3 +-- .../org/apache/dubbo/remoting/Transporter.java | 6 ++---- .../org/apache/dubbo/rpc/InvokerListener.java | 3 +-- .../hessian/HttpClientConnectionFactory.java | 3 --- pom.xml | 15 +++------------ 6 files changed, 8 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index fe60e30958c..26e0783122d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ cache: install: true script: - - travis_wait 30 ./mvnw clean package -DskipTests=false -Dcheckstyle.skip=true -Drat.skip=true -Dmaven.javadoc.skip=true + - travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=false -Drat.skip=false -Dmaven.javadoc.skip=true after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterFactory.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterFactory.java index 679b9fe589d..c81ba5465db 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterFactory.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterFactory.java @@ -19,7 +19,6 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI; -import org.apache.dubbo.rpc.Invocation; /** * RouterFactory. (SPI, Singleton, ThreadSafe) @@ -27,7 +26,7 @@ * Routing * * @see org.apache.dubbo.rpc.cluster.Cluster#join(Directory) - * @see org.apache.dubbo.rpc.cluster.Directory#list(Invocation) + * @see org.apache.dubbo.rpc.cluster.Directory#list(org.apache.dubbo.rpc.Invocation) */ @SPI public interface RouterFactory { diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java index b53c75f9c21..33ac2bedd3f 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/Transporter.java @@ -21,8 +21,6 @@ import org.apache.dubbo.common.extension.Adaptive; import org.apache.dubbo.common.extension.SPI; -import javax.sound.midi.Receiver; - /** * Transporter. (SPI, Singleton, ThreadSafe) *

@@ -41,7 +39,7 @@ public interface Transporter { * @param handler * @return server * @throws RemotingException - * @see org.apache.dubbo.remoting.Transporters#bind(URL, Receiver, ChannelHandler) + * @see org.apache.dubbo.remoting.Transporters#bind(URL, ChannelHandler...) */ @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind(URL url, ChannelHandler handler) throws RemotingException; @@ -53,7 +51,7 @@ public interface Transporter { * @param handler * @return client * @throws RemotingException - * @see org.apache.dubbo.remoting.Transporters#connect(URL, Receiver, ChannelListener) + * @see org.apache.dubbo.remoting.Transporters#connect(URL, ChannelHandler...) */ @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect(URL url, ChannelHandler handler) throws RemotingException; diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvokerListener.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvokerListener.java index 6842dd87506..5516b078363 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvokerListener.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvokerListener.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc; -import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.SPI; /** @@ -30,7 +29,7 @@ public interface InvokerListener { * * @param invoker * @throws RpcException - * @see org.apache.dubbo.rpc.Protocol#refer(Class, URL) + * @see org.apache.dubbo.rpc.Protocol#refer(Class, org.apache.dubbo.common.URL) */ void referred(Invoker invoker) throws RpcException; diff --git a/dubbo-rpc/dubbo-rpc-hessian/src/main/java/org/apache/dubbo/rpc/protocol/hessian/HttpClientConnectionFactory.java b/dubbo-rpc/dubbo-rpc-hessian/src/main/java/org/apache/dubbo/rpc/protocol/hessian/HttpClientConnectionFactory.java index 36b1e2b1eb5..2c6f6db890b 100644 --- a/dubbo-rpc/dubbo-rpc-hessian/src/main/java/org/apache/dubbo/rpc/protocol/hessian/HttpClientConnectionFactory.java +++ b/dubbo-rpc/dubbo-rpc-hessian/src/main/java/org/apache/dubbo/rpc/protocol/hessian/HttpClientConnectionFactory.java @@ -24,11 +24,8 @@ import com.caucho.hessian.client.HessianProxyFactory; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; -import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.params.HttpConnectionParams; -import java.io.IOException; import java.net.URL; /** diff --git a/pom.xml b/pom.xml index 16713a87cba..5bd895dc60f 100644 --- a/pom.xml +++ b/pom.xml @@ -261,10 +261,10 @@ codestyle/checkstyle.xml codestyle/checkstyle-suppressions.xml - codestyle/checkstyle-header.txt UTF-8 true true + **/JSONWriter.java check @@ -324,20 +324,11 @@ - src/main/resources/ + src/main/resources false - ../ - META-INF/ - false - - NOTICE - LICENSE - - - - ../../ + ${maven.multiModuleProjectDirectory} META-INF/ false From 86d734722d7309fca22499a7b7ccd2a42570d3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=9A=AE=E7=9A=AE?= <363230482@qq.com> Date: Tue, 28 Aug 2018 17:31:22 +0800 Subject: [PATCH 09/12] typo: prvoiderService->providerService; connectSkiped->connectSkipped (#2396) --- .../apache/dubbo/registry/redis/RedisRegistry.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dubbo-registry/dubbo-registry-redis/src/main/java/org/apache/dubbo/registry/redis/RedisRegistry.java b/dubbo-registry/dubbo-registry-redis/src/main/java/org/apache/dubbo/registry/redis/RedisRegistry.java index 98b9de176f1..b2be1647878 100644 --- a/dubbo-registry/dubbo-registry-redis/src/main/java/org/apache/dubbo/registry/redis/RedisRegistry.java +++ b/dubbo-registry/dubbo-registry-redis/src/main/java/org/apache/dubbo/registry/redis/RedisRegistry.java @@ -408,8 +408,8 @@ private void doNotify(Jedis jedis, Collection keys, URL url, Collection< String consumerService = url.getServiceInterface(); for (String key : keys) { if (!Constants.ANY_VALUE.equals(consumerService)) { - String prvoiderService = toServiceName(key); - if (!prvoiderService.equals(consumerService)) { + String providerService = toServiceName(key); + if (!providerService.equals(consumerService)) { continue; } } @@ -532,7 +532,7 @@ private class Notifier extends Thread { private final String service; private final AtomicInteger connectSkip = new AtomicInteger(); - private final AtomicInteger connectSkiped = new AtomicInteger(); + private final AtomicInteger connectSkipped = new AtomicInteger(); private final Random random = new Random(); private volatile Jedis jedis; private volatile boolean first = true; @@ -547,7 +547,7 @@ public Notifier(String service) { private void resetSkip() { connectSkip.set(0); - connectSkiped.set(0); + connectSkipped.set(0); connectRandom = 0; } @@ -559,11 +559,11 @@ private boolean isSkip() { } skip = 10 + connectRandom; } - if (connectSkiped.getAndIncrement() < skip) { // Check the number of skipping times + if (connectSkipped.getAndIncrement() < skip) { // Check the number of skipping times return true; } connectSkip.incrementAndGet(); - connectSkiped.set(0); + connectSkipped.set(0); connectRandom = 0; return false; } From ff373a7db7645ea1b55bf84150d0326d1e999b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E6=97=A0=E4=B8=A4=E4=B8=B6?= <442367943@qq.com> Date: Tue, 28 Aug 2018 18:06:49 +0800 Subject: [PATCH 10/12] New ascii logo. (#2395) --- .../org/apache/dubbo/qos/server/DubboLogo.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/DubboLogo.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/DubboLogo.java index e25613a160b..b504cbf335b 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/DubboLogo.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/DubboLogo.java @@ -17,15 +17,9 @@ package org.apache.dubbo.qos.server; public class DubboLogo { - public static String dubbo = - " ████████▄ ███ █▄ ▀█████████▄ ▀█████████▄ ▄██████▄ \n" + - " ███ ▀███ ███ ███ ███ ███ ███ ███ ███ ███ \n" + - " ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ \n" + - " ███ ███ ███ ███ ▄███▄▄▄██▀ ▄███▄▄▄██▀ ███ ███ \n" + - " ███ ███ ███ ███ ▀▀███▀▀▀██▄ ▀▀███▀▀▀██▄ ███ ███ \n" + - " ███ ███ ███ ███ ███ ██▄ ███ ██▄ ███ ███ \n" + - " ███ ▄███ ███ ███ ███ ███ ███ ███ ███ ███ \n" + - " ████████▀ ████████▀ ▄█████████▀ ▄█████████▀ ▀██████▀ \n" + - " \n" + - "\n"; + public static final String dubbo = + " ___ __ __ ___ ___ ____ \n" + + " / _ \\ / / / // _ ) / _ ) / __ \\ \n" + + " / // // /_/ // _ |/ _ |/ /_/ / \n" + + "/____/ \\____//____//____/ \\____/ \n"; } From a3e45933431d1ca2771101f6e98fa342857b04af Mon Sep 17 00:00:00 2001 From: jinzeyu0 <31379376+jinzeyu0@users.noreply.github.com> Date: Fri, 31 Aug 2018 18:04:02 +0800 Subject: [PATCH 11/12] Merge pull request #2360, add xml configuration for graceful shutdown on ApplicationConfig level. --- .../org/apache/dubbo/config/ApplicationConfig.java | 13 +++++++++++++ .../src/main/resources/META-INF/compat/dubbo.xsd | 6 ++++++ 2 files changed, 19 insertions(+) diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java index 86d40012dc8..71a172535a8 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java @@ -83,6 +83,10 @@ public class ApplicationConfig extends AbstractConfig { // customized parameters private Map parameters; + //config the shutdown.wait + private String shutwait; + + public ApplicationConfig() { } @@ -254,4 +258,13 @@ public void setParameters(Map parameters) { checkParameterName(parameters); this.parameters = parameters; } + + public String getShutwait() { + return shutwait; + } + + public void setShutwait(String shutwait) { + System.setProperty( Constants.SHUTDOWN_WAIT_KEY, shutwait); + this.shutwait = shutwait; + } } \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd index 290c750c93b..59245913fd8 100644 --- a/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd +++ b/dubbo-config/dubbo-config-spring/src/main/resources/META-INF/compat/dubbo.xsd @@ -377,11 +377,17 @@ + + + + + + From 2ae2f582a155a8ab76ea3f599ad31548d4001fbb Mon Sep 17 00:00:00 2001 From: Li Han Date: Fri, 31 Aug 2018 20:10:28 +0800 Subject: [PATCH 12/12] Merge pull request #2378, add unit test for HeaderExchanngeChannel. --- .../header/HeaderExchangeChannelTest.java | 284 ++++++++++++++++++ .../exchange/support/header/MockChannel.java | 4 + 2 files changed, 288 insertions(+) create mode 100644 dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannelTest.java diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannelTest.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannelTest.java new file mode 100644 index 00000000000..8572f89087a --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeaderExchangeChannelTest.java @@ -0,0 +1,284 @@ +/* + * 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.dubbo.remoting.exchange.support.header; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.exchange.support.DefaultFuture; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.apache.dubbo.remoting.Channel; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.exchange.Request; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import java.util.List; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +public class HeaderExchangeChannelTest { + + private HeaderExchangeChannel header; + private MockChannel channel; + private URL url = URL.valueOf("dubbo://localhost:20880"); + private static final String CHANNEL_KEY = HeaderExchangeChannel.class.getName() + ".CHANNEL"; + + @Before + public void setup() { + channel = new MockChannel() { + @Override + public URL getUrl() { + return url; + } + }; + + header = new HeaderExchangeChannel(channel); + } + + @Test + public void getOrAddChannelTest00() { + channel.setAttribute("CHANNEL_KEY", "attribute"); + HeaderExchangeChannel ret = HeaderExchangeChannel.getOrAddChannel(channel); + Assert.assertNotNull(ret); + } + + @Test + public void getOrAddChannelTest01() { + channel = new MockChannel(){ + @Override + public URL getUrl(){ + return url; + } + + @Override + public boolean isConnected(){ + return true; + } + + }; + Assert.assertNull(channel.getAttribute(CHANNEL_KEY)); + HeaderExchangeChannel ret = HeaderExchangeChannel.getOrAddChannel(channel); + Assert.assertNotNull(ret); + Assert.assertNotNull(channel.getAttribute(CHANNEL_KEY)); + Assert.assertEquals(channel.getAttribute(CHANNEL_KEY).getClass(), HeaderExchangeChannel.class); + } + + @Test + public void getOrAddChannelTest02() { + channel = null; + HeaderExchangeChannel ret = HeaderExchangeChannel.getOrAddChannel(channel); + Assert.assertNull(ret); + } + + + @Test + public void removeChannelIfDisconnectedTest() { + Assert.assertNull(channel.getAttribute(CHANNEL_KEY)); + channel.setAttribute(CHANNEL_KEY, header); + channel.close(); + HeaderExchangeChannel.removeChannelIfDisconnected(channel); + Assert.assertNull(channel.getAttribute(CHANNEL_KEY)); + } + + @Test + public void sendTest00() { + boolean sent = true; + String message = "this is a test message"; + try { + header.close(1); + header.send(message, sent); + } catch (Exception e) { + Assert.assertTrue(e instanceof RemotingException); + } + } + + @Test + public void sendTest01() throws RemotingException { + boolean sent = true; + String message = "this is a test message"; + header.send(message, sent); + List objects = channel.getSentObjects(); + Assert.assertEquals(objects.get(0), "this is a test message"); + } + + @Test + public void sendTest02() throws RemotingException { + boolean sent = true; + int message = 1; + header.send(message, sent); + List objects = channel.getSentObjects(); + Assert.assertEquals(objects.get(0).getClass(), Request.class); + Request request = (Request) objects.get(0); + Assert.assertEquals(request.getVersion(), "2.0.2"); + } + + @Test + public void sendTest04() throws RemotingException { + String message = "this is a test message"; + header.send(message); + List objects = channel.getSentObjects(); + Assert.assertEquals(objects.get(0), "this is a test message"); + } + + @Test(expected = RemotingException.class) + public void requestTest01() throws RemotingException { + header.close(1000); + Object requestob = new Object(); + header.request(requestob); + } + + @Test + public void requestTest02() throws RemotingException { + Channel channel = Mockito.mock(MockChannel.class); + header = new HeaderExchangeChannel(channel); + when(channel.getUrl()).thenReturn(url); + Object requestob = new Object(); + header.request(requestob); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Request.class); + verify(channel, times(1)).send(argumentCaptor.capture()); + Assert.assertEquals(argumentCaptor.getValue().getData(), requestob); + } + + @Test(expected = RemotingException.class) + public void requestTest03() throws RemotingException{ + channel = new MockChannel() { + @Override + public void send(Object req) throws RemotingException { + throw new RemotingException(channel.getLocalAddress(), channel.getRemoteAddress(), "throw error"); + } + }; + header = new HeaderExchangeChannel(channel); + Object requestob = new Object(); + header.request(requestob, 1000); + } + + @Test + public void isClosedTest(){ + Assert.assertFalse(header.isClosed()); + } + + @Test + public void closeTest() { + Assert.assertFalse(channel.isClosed()); + header.close(); + Assert.assertTrue(channel.isClosed()); + } + + + @Test + public void closeWithTimeoutTest02() { + Assert.assertFalse(channel.isClosed()); + Request request = new Request(); + DefaultFuture.newFuture(channel, request, 100); + header.close(100); + //return directly + header.close(1000); + } + + + @Test + public void startCloseTest() { + try { + boolean isClosing = channel.isClosing(); + Assert.assertFalse(isClosing); + header.startClose(); + isClosing = channel.isClosing(); + Assert.assertTrue(isClosing); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void getLocalAddressTest() { + Assert.assertNull(header.getLocalAddress()); + } + + @Test + public void getRemoteAddressTest() { + Assert.assertNull(header.getRemoteAddress()); + } + + @Test + public void getUrlTest() { + Assert.assertEquals(header.getUrl(), URL.valueOf("dubbo://localhost:20880")); + } + + @Test + public void isConnectedTest() { + Assert.assertFalse(header.isConnected()); + } + + + @Test + public void getChannelHandlerTest() { + Assert.assertNull(header.getChannelHandler()); + } + + @Test + public void getExchangeHandlerTest() { + Assert.assertNull(header.getExchangeHandler()); + } + + + @Test + public void getAttributeAndSetAttributeTest() { + header.setAttribute("test", "test"); + Assert.assertEquals(header.getAttribute("test"), "test"); + Assert.assertTrue(header.hasAttribute("test")); + } + + @Test + public void removeAttributeTest() { + header.setAttribute("test", "test"); + Assert.assertEquals(header.getAttribute("test"), "test"); + header.removeAttribute("test"); + Assert.assertFalse(header.hasAttribute("test")); + } + + + @Test + public void hasAttributeTest() { + Assert.assertFalse(header.hasAttribute("test")); + header.setAttribute("test", "test"); + Assert.assertTrue(header.hasAttribute("test")); + } + + @Test + public void hashCodeTest(){ + final int prime = 31; + int result = 1; + result = prime * result + ((channel == null) ? 0 : channel.hashCode()); + + Assert.assertEquals(header.hashCode(),result); + } + + @Test(expected = IllegalArgumentException.class) + public void equalsTest(){ + Assert.assertEquals(header, new HeaderExchangeChannel(channel)); + header = new HeaderExchangeChannel(null); + Assert.assertNotEquals(header, new HeaderExchangeChannel(channel)); + } + + + @Test + public void toStringTest(){ + Assert.assertEquals(header.toString(),channel.toString()); + } +} + diff --git a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/MockChannel.java b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/MockChannel.java index df750e0af5e..7c0f7333705 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/MockChannel.java +++ b/dubbo-remoting/dubbo-remoting-api/src/test/java/org/apache/dubbo/remoting/exchange/support/header/MockChannel.java @@ -115,4 +115,8 @@ public boolean isClosed() { public List getSentObjects() { return Collections.unmodifiableList(sentObjects); } + + public boolean isClosing() { + return closing; + } }