Resolves checkstyle errors for patterns starting with letter r (#1072)
* Reduces checkstyle errors in reactor * Reduces checkstyle errors in reader-writer-lock * Reduces checkstyle errors in repository * Reduces checkstyle errors in resource-acquisition-is-initialization * Reduces checkstyle errors in retry
This commit is contained in:
parent
4dae1fae57
commit
9c8ad4485b
@ -23,10 +23,6 @@
|
||||
|
||||
package com.iluwatar.reactor.app;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.iluwatar.reactor.framework.AbstractNioChannel;
|
||||
import com.iluwatar.reactor.framework.ChannelHandler;
|
||||
import com.iluwatar.reactor.framework.Dispatcher;
|
||||
@ -34,19 +30,20 @@ import com.iluwatar.reactor.framework.NioDatagramChannel;
|
||||
import com.iluwatar.reactor.framework.NioReactor;
|
||||
import com.iluwatar.reactor.framework.NioServerSocketChannel;
|
||||
import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This application demonstrates Reactor pattern. The example demonstrated is a Distributed Logging
|
||||
* Service where it listens on multiple TCP or UDP sockets for incoming log requests.
|
||||
*
|
||||
* <p>
|
||||
* <i>INTENT</i> <br>
|
||||
*
|
||||
* <p><i>INTENT</i> <br>
|
||||
* The Reactor design pattern handles service requests that are delivered concurrently to an
|
||||
* application by one or more clients. The application can register specific handlers for processing
|
||||
* which are called by reactor on specific events.
|
||||
*
|
||||
* <p>
|
||||
* <i>PROBLEM</i> <br>
|
||||
*
|
||||
* <p><i>PROBLEM</i> <br>
|
||||
* Server applications in a distributed system must handle multiple clients that send them service
|
||||
* requests. Following forces need to be resolved:
|
||||
* <ul>
|
||||
@ -55,9 +52,8 @@ import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
|
||||
* <li>Programming Simplicity</li>
|
||||
* <li>Adaptability</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* <i>PARTICIPANTS</i> <br>
|
||||
*
|
||||
* <p><i>PARTICIPANTS</i> <br>
|
||||
* <ul>
|
||||
* <li>Synchronous Event De-multiplexer
|
||||
* <p>
|
||||
@ -89,7 +85,6 @@ import com.iluwatar.reactor.framework.ThreadPoolDispatcher;
|
||||
* separate thread for each client, which provides better scalability under load (number of clients
|
||||
* increase).
|
||||
* The example uses Java NIO framework to implement the Reactor.
|
||||
*
|
||||
*/
|
||||
public class App {
|
||||
|
||||
@ -100,7 +95,7 @@ public class App {
|
||||
/**
|
||||
* Creates an instance of App which will use provided dispatcher for dispatching events on
|
||||
* reactor.
|
||||
*
|
||||
*
|
||||
* @param dispatcher the dispatcher that will be used to dispatch events.
|
||||
*/
|
||||
public App(Dispatcher dispatcher) {
|
||||
@ -142,9 +137,9 @@ public class App {
|
||||
|
||||
/**
|
||||
* Stops the NIO reactor. This is a blocking call.
|
||||
*
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping the reactor.
|
||||
* @throws IOException if any I/O error occurs
|
||||
* @throws IOException if any I/O error occurs
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactor.stop();
|
||||
|
@ -23,9 +23,6 @@
|
||||
|
||||
package com.iluwatar.reactor.app;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -39,6 +36,8 @@ import java.net.UnknownHostException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents the clients of Reactor pattern. Multiple clients are run concurrently and send logging
|
||||
@ -158,7 +157,7 @@ public class AppClient {
|
||||
* Creates a new UDP logging client.
|
||||
*
|
||||
* @param clientName the name of the client to be sent in logging requests.
|
||||
* @param port the port on which client will send logging requests.
|
||||
* @param port the port on which client will send logging requests.
|
||||
* @throws UnknownHostException if localhost is unknown
|
||||
*/
|
||||
public UdpLoggingClient(String clientName, int port) throws UnknownHostException {
|
||||
|
@ -23,12 +23,11 @@
|
||||
|
||||
package com.iluwatar.reactor.app;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
|
||||
import com.iluwatar.reactor.framework.AbstractNioChannel;
|
||||
import com.iluwatar.reactor.framework.ChannelHandler;
|
||||
import com.iluwatar.reactor.framework.NioDatagramChannel.DatagramPacket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -63,7 +62,11 @@ public class LoggingHandler implements ChannelHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendReply(AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) {
|
||||
private static void sendReply(
|
||||
AbstractNioChannel channel,
|
||||
DatagramPacket incomingPacket,
|
||||
SelectionKey key
|
||||
) {
|
||||
/*
|
||||
* Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming
|
||||
* message.
|
||||
|
@ -35,13 +35,12 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
/**
|
||||
* This represents the <i>Handle</i> of Reactor pattern. These are resources managed by OS which can
|
||||
* be submitted to {@link NioReactor}.
|
||||
*
|
||||
* <p>
|
||||
* This class serves has the responsibility of reading the data when a read event occurs and writing
|
||||
* the data back when the channel is writable. It leaves the reading and writing of data on the
|
||||
* concrete implementation. It provides a block writing mechanism wherein when any
|
||||
* {@link ChannelHandler} wants to write data back, it queues the data in pending write queue and
|
||||
* clears it in block manner. This provides better throughput.
|
||||
*
|
||||
* <p>This class serves has the responsibility of reading the data when a read event occurs and
|
||||
* writing the data back when the channel is writable. It leaves the reading and writing of data on
|
||||
* the concrete implementation. It provides a block writing mechanism wherein when any {@link
|
||||
* ChannelHandler} wants to write data back, it queues the data in pending write queue and clears it
|
||||
* in block manner. This provides better throughput.
|
||||
*/
|
||||
public abstract class AbstractNioChannel {
|
||||
|
||||
@ -53,7 +52,7 @@ public abstract class AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Creates a new channel.
|
||||
*
|
||||
*
|
||||
* @param handler which will handle events occurring on this channel.
|
||||
* @param channel a NIO channel to be wrapped.
|
||||
*/
|
||||
@ -70,6 +69,8 @@ public abstract class AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get channel.
|
||||
*
|
||||
* @return the wrapped NIO channel.
|
||||
*/
|
||||
public SelectableChannel getJavaChannel() {
|
||||
@ -77,9 +78,9 @@ public abstract class AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* The operation in which the channel is interested, this operation is provided to
|
||||
* {@link Selector}.
|
||||
*
|
||||
* The operation in which the channel is interested, this operation is provided to {@link
|
||||
* Selector}.
|
||||
*
|
||||
* @return interested operation.
|
||||
* @see SelectionKey
|
||||
*/
|
||||
@ -87,7 +88,7 @@ public abstract class AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Binds the channel on provided port.
|
||||
*
|
||||
*
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public abstract void bind() throws IOException;
|
||||
@ -95,7 +96,7 @@ public abstract class AbstractNioChannel {
|
||||
/**
|
||||
* Reads the data using the key and returns the read data. The underlying channel should be
|
||||
* fetched using {@link SelectionKey#channel()}.
|
||||
*
|
||||
*
|
||||
* @param key the key on which read event occurred.
|
||||
* @return data read.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
@ -103,6 +104,8 @@ public abstract class AbstractNioChannel {
|
||||
public abstract Object read(SelectionKey key) throws IOException;
|
||||
|
||||
/**
|
||||
* Get handler.
|
||||
*
|
||||
* @return the handler associated with this channel.
|
||||
*/
|
||||
public ChannelHandler getHandler() {
|
||||
@ -130,9 +133,9 @@ public abstract class AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Writes the data to the channel.
|
||||
*
|
||||
*
|
||||
* @param pendingWrite the data to be written on channel.
|
||||
* @param key the key which is writable.
|
||||
* @param key the key which is writable.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
protected abstract void doWrite(Object pendingWrite, SelectionKey key) throws IOException;
|
||||
@ -140,24 +143,23 @@ public abstract class AbstractNioChannel {
|
||||
/**
|
||||
* Queues the data for writing. The data is not guaranteed to be written on underlying channel
|
||||
* when this method returns. It will be written when the channel is flushed.
|
||||
*
|
||||
* <p>
|
||||
* This method is used by the {@link ChannelHandler} to send reply back to the client. <br>
|
||||
*
|
||||
* <p>This method is used by the {@link ChannelHandler} to send reply back to the client. <br>
|
||||
* Example:
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* {@literal @}Override
|
||||
* public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
* byte[] data = ((ByteBuffer)readObject).array();
|
||||
* public void handleChannelRead(AbstractNioChannel channel, Object readObj, SelectionKey key) {
|
||||
* byte[] data = ((ByteBuffer)readObj).array();
|
||||
* ByteBuffer buffer = ByteBuffer.wrap("Server reply".getBytes());
|
||||
* channel.write(buffer, key);
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param data the data to be written on underlying channel.
|
||||
* @param key the key which is writable.
|
||||
* @param key the key which is writable.
|
||||
*/
|
||||
public void write(Object data, SelectionKey key) {
|
||||
Queue<Object> pendingWrites = this.channelToPendingWrites.get(key.channel());
|
||||
|
@ -28,19 +28,19 @@ import java.nio.channels.SelectionKey;
|
||||
/**
|
||||
* Represents the <i>EventHandler</i> of Reactor pattern. It handles the incoming events dispatched
|
||||
* to it by the {@link Dispatcher}. This is where the application logic resides.
|
||||
*
|
||||
* <p>
|
||||
* A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and
|
||||
* whenever an event occurs on any of the associated channels, the handler is notified of the event.
|
||||
*
|
||||
* <p>A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and
|
||||
* whenever an event occurs on any of the associated channels, the handler is notified of the
|
||||
* event.
|
||||
*/
|
||||
public interface ChannelHandler {
|
||||
|
||||
/**
|
||||
* Called when the {@code channel} receives some data from remote peer.
|
||||
*
|
||||
* @param channel the channel from which the data was received.
|
||||
*
|
||||
* @param channel the channel from which the data was received.
|
||||
* @param readObject the data read.
|
||||
* @param key the key on which read event occurred.
|
||||
* @param key the key on which read event occurred.
|
||||
*/
|
||||
void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key);
|
||||
}
|
||||
|
@ -29,14 +29,12 @@ import java.nio.channels.SelectionKey;
|
||||
* Represents the event dispatching strategy. When {@link NioReactor} senses any event on the
|
||||
* registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write or
|
||||
* connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the
|
||||
* I/O processing from application specific processing. <br>
|
||||
* Dispatcher should call the {@link ChannelHandler} associated with the channel on which event
|
||||
* occurred.
|
||||
*
|
||||
* <p>
|
||||
* The application can customize the way in which event is dispatched such as using the reactor
|
||||
* I/O processing from application specific processing. <br> Dispatcher should call the {@link
|
||||
* ChannelHandler} associated with the channel on which event occurred.
|
||||
*
|
||||
* <p>The application can customize the way in which event is dispatched such as using the reactor
|
||||
* thread to dispatch event to channels or use a worker pool to do the non I/O processing.
|
||||
*
|
||||
*
|
||||
* @see SameThreadDispatcher
|
||||
* @see ThreadPoolDispatcher
|
||||
*/
|
||||
@ -45,19 +43,18 @@ public interface Dispatcher {
|
||||
* This hook method is called when read event occurs on particular channel. The data read is
|
||||
* provided in <code>readObject</code>. The implementation should dispatch this read event to the
|
||||
* associated {@link ChannelHandler} of <code>channel</code>.
|
||||
*
|
||||
* <p>
|
||||
* The type of <code>readObject</code> depends on the channel on which data was received.
|
||||
*
|
||||
* @param channel on which read event occurred
|
||||
*
|
||||
* <p>The type of <code>readObject</code> depends on the channel on which data was received.
|
||||
*
|
||||
* @param channel on which read event occurred
|
||||
* @param readObject object read by channel
|
||||
* @param key on which event occurred
|
||||
* @param key on which event occurred
|
||||
*/
|
||||
void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key);
|
||||
|
||||
/**
|
||||
* Stops dispatching events and cleans up any acquired resources such as threads.
|
||||
*
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping dispatcher.
|
||||
*/
|
||||
void stop() throws InterruptedException;
|
||||
|
@ -23,9 +23,6 @@
|
||||
|
||||
package com.iluwatar.reactor.framework;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -33,6 +30,8 @@ import java.net.SocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel.
|
||||
@ -46,11 +45,11 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
/**
|
||||
* Creates a {@link DatagramChannel} which will bind at provided port and use <code>handler</code>
|
||||
* to handle incoming events on this channel.
|
||||
* <p>
|
||||
* Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
*
|
||||
* <p>Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
* binding the socket.
|
||||
*
|
||||
* @param port the port to be bound to listen for incoming datagram requests.
|
||||
*
|
||||
* @param port the port to be bound to listen for incoming datagram requests.
|
||||
* @param handler the handler to be used for handling incoming requests on this channel.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@ -69,7 +68,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Reads and returns a {@link DatagramPacket} from the underlying channel.
|
||||
*
|
||||
*
|
||||
* @return the datagram packet read having the sender address.
|
||||
*/
|
||||
@Override
|
||||
@ -89,6 +88,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get datagram channel.
|
||||
*
|
||||
* @return the underlying datagram channel.
|
||||
*/
|
||||
@Override
|
||||
@ -98,7 +99,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Binds UDP socket on the provided <code>port</code>.
|
||||
*
|
||||
*
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
@ -120,8 +121,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Writes the outgoing {@link DatagramPacket} to the channel. The intended receiver of the
|
||||
* datagram packet must be set in the <code>data</code> using
|
||||
* {@link DatagramPacket#setReceiver(SocketAddress)}.
|
||||
* datagram packet must be set in the <code>data</code> using {@link
|
||||
* DatagramPacket#setReceiver(SocketAddress)}.
|
||||
*/
|
||||
@Override
|
||||
public void write(Object data, SelectionKey key) {
|
||||
@ -138,7 +139,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Creates a container with underlying data.
|
||||
*
|
||||
*
|
||||
* @param data the underlying message to be written on channel.
|
||||
*/
|
||||
public DatagramPacket(ByteBuffer data) {
|
||||
@ -146,6 +147,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender address.
|
||||
*
|
||||
* @return the sender address.
|
||||
*/
|
||||
public SocketAddress getSender() {
|
||||
@ -154,7 +157,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Sets the sender address of this packet.
|
||||
*
|
||||
*
|
||||
* @param sender the sender address.
|
||||
*/
|
||||
public void setSender(SocketAddress sender) {
|
||||
@ -162,6 +165,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get receiver address.
|
||||
*
|
||||
* @return the receiver address.
|
||||
*/
|
||||
public SocketAddress getReceiver() {
|
||||
@ -170,7 +175,7 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Sets the intended receiver address. This must be set when writing to the channel.
|
||||
*
|
||||
*
|
||||
* @param receiver the receiver address.
|
||||
*/
|
||||
public void setReceiver(SocketAddress receiver) {
|
||||
@ -178,6 +183,8 @@ public class NioDatagramChannel extends AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data.
|
||||
*
|
||||
* @return the underlying message that will be written on channel.
|
||||
*/
|
||||
public ByteBuffer getData() {
|
||||
|
@ -23,9 +23,6 @@
|
||||
|
||||
package com.iluwatar.reactor.framework;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
@ -38,22 +35,23 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern. Multiple handles
|
||||
* i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks for events from all these handles.
|
||||
* Whenever an event occurs on any of the registered handles, it synchronously de-multiplexes the event which can be any
|
||||
* of read, write or accept, and dispatches the event to the appropriate {@link ChannelHandler} using the
|
||||
* {@link Dispatcher}.
|
||||
*
|
||||
* <p>
|
||||
* Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} method.
|
||||
* {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing.
|
||||
*
|
||||
* <p>
|
||||
* NOTE: This is one of the ways to implement NIO reactor and it does not take care of all possible edge cases which are
|
||||
* required in a real application. This implementation is meant to demonstrate the fundamental concepts that lie behind
|
||||
* Reactor pattern.
|
||||
* This class acts as Synchronous Event De-multiplexer and Initiation Dispatcher of Reactor pattern.
|
||||
* Multiple handles i.e. {@link AbstractNioChannel}s can be registered to the reactor and it blocks
|
||||
* for events from all these handles. Whenever an event occurs on any of the registered handles, it
|
||||
* synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches
|
||||
* the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}.
|
||||
*
|
||||
* <p>Implementation: A NIO reactor runs in its own thread when it is started using {@link
|
||||
* #start()} method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event
|
||||
* De-multiplexing.
|
||||
*
|
||||
* <p>NOTE: This is one of the ways to implement NIO reactor and it does not take care of all
|
||||
* possible edge cases which are required in a real application. This implementation is meant to
|
||||
* demonstrate the fundamental concepts that lie behind Reactor pattern.
|
||||
*/
|
||||
public class NioReactor {
|
||||
|
||||
@ -62,21 +60,20 @@ public class NioReactor {
|
||||
private final Selector selector;
|
||||
private final Dispatcher dispatcher;
|
||||
/**
|
||||
* All the work of altering the SelectionKey operations and Selector operations are performed in the context of main
|
||||
* event loop of reactor. So when any channel needs to change its readability or writability, a new command is added
|
||||
* in the command queue and then the event loop picks up the command and executes it in next iteration.
|
||||
* All the work of altering the SelectionKey operations and Selector operations are performed in
|
||||
* the context of main event loop of reactor. So when any channel needs to change its readability
|
||||
* or writability, a new command is added in the command queue and then the event loop picks up
|
||||
* the command and executes it in next iteration.
|
||||
*/
|
||||
private final Queue<Runnable> pendingCommands = new ConcurrentLinkedQueue<>();
|
||||
private final ExecutorService reactorMain = Executors.newSingleThreadExecutor();
|
||||
|
||||
/**
|
||||
* Creates a reactor which will use provided {@code dispatcher} to dispatch events. The application can provide
|
||||
* various implementations of dispatcher which suits its needs.
|
||||
*
|
||||
* @param dispatcher
|
||||
* a non-null dispatcher used to dispatch events on registered channels.
|
||||
* @throws IOException
|
||||
* if any I/O error occurs.
|
||||
* Creates a reactor which will use provided {@code dispatcher} to dispatch events. The
|
||||
* application can provide various implementations of dispatcher which suits its needs.
|
||||
*
|
||||
* @param dispatcher a non-null dispatcher used to dispatch events on registered channels.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public NioReactor(Dispatcher dispatcher) throws IOException {
|
||||
this.dispatcher = dispatcher;
|
||||
@ -99,11 +96,9 @@ public class NioReactor {
|
||||
|
||||
/**
|
||||
* Stops the reactor and related resources such as dispatcher.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
* if interrupted while stopping the reactor.
|
||||
* @throws IOException
|
||||
* if any I/O error occurs.
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping the reactor.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public void stop() throws InterruptedException, IOException {
|
||||
reactorMain.shutdownNow();
|
||||
@ -114,15 +109,14 @@ public class NioReactor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new channel (handle) with this reactor. Reactor will start waiting for events on this channel and
|
||||
* notify of any events. While registering the channel the reactor uses {@link AbstractNioChannel#getInterestedOps()}
|
||||
* to know about the interested operation of this channel.
|
||||
*
|
||||
* @param channel
|
||||
* a new channel on which reactor will wait for events. The channel must be bound prior to being registered.
|
||||
* Registers a new channel (handle) with this reactor. Reactor will start waiting for events on
|
||||
* this channel and notify of any events. While registering the channel the reactor uses {@link
|
||||
* AbstractNioChannel#getInterestedOps()} to know about the interested operation of this channel.
|
||||
*
|
||||
* @param channel a new channel on which reactor will wait for events. The channel must be bound
|
||||
* prior to being registered.
|
||||
* @return this
|
||||
* @throws IOException
|
||||
* if any I/O error occurs.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
public NioReactor registerChannel(AbstractNioChannel channel) throws IOException {
|
||||
SelectionKey key = channel.getJavaChannel().register(selector, channel.getInterestedOps());
|
||||
@ -143,8 +137,8 @@ public class NioReactor {
|
||||
processPendingCommands();
|
||||
|
||||
/*
|
||||
* Synchronous event de-multiplexing happens here, this is blocking call which returns when it is possible to
|
||||
* initiate non-blocking operation on any of the registered channels.
|
||||
* Synchronous event de-multiplexing happens here, this is blocking call which returns when it
|
||||
* is possible to initiate non-blocking operation on any of the registered channels.
|
||||
*/
|
||||
selector.select();
|
||||
|
||||
@ -177,8 +171,8 @@ public class NioReactor {
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiation dispatcher logic, it checks the type of event and notifier application specific event handler to handle
|
||||
* the event.
|
||||
* Initiation dispatcher logic, it checks the type of event and notifier application specific
|
||||
* event handler to handle the event.
|
||||
*/
|
||||
private void processKey(SelectionKey key) throws IOException {
|
||||
if (key.isAcceptable()) {
|
||||
@ -226,15 +220,14 @@ public class NioReactor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues the change of operations request of a channel, which will change the interested operations of the channel
|
||||
* sometime in future.
|
||||
* <p>
|
||||
* This is a non-blocking method and does not guarantee that the operations have changed when this method returns.
|
||||
*
|
||||
* @param key
|
||||
* the key for which operations have to be changed.
|
||||
* @param interestedOps
|
||||
* the new interest operations.
|
||||
* Queues the change of operations request of a channel, which will change the interested
|
||||
* operations of the channel sometime in future.
|
||||
*
|
||||
* <p>This is a non-blocking method and does not guarantee that the operations have changed when
|
||||
* this method returns.
|
||||
*
|
||||
* @param key the key for which operations have to be changed.
|
||||
* @param interestedOps the new interest operations.
|
||||
*/
|
||||
public void changeOps(SelectionKey key, int interestedOps) {
|
||||
pendingCommands.add(new ChangeKeyOpsCommand(key, interestedOps));
|
||||
|
@ -23,9 +23,6 @@
|
||||
|
||||
package com.iluwatar.reactor.framework;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
@ -33,10 +30,12 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A wrapper over {@link NioServerSocketChannel} which can read and write data on a
|
||||
* {@link SocketChannel}.
|
||||
* A wrapper over {@link NioServerSocketChannel} which can read and write data on a {@link
|
||||
* SocketChannel}.
|
||||
*/
|
||||
public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
|
||||
@ -47,11 +46,11 @@ public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
/**
|
||||
* Creates a {@link ServerSocketChannel} which will bind at provided port and use
|
||||
* <code>handler</code> to handle incoming events on this channel.
|
||||
* <p>
|
||||
* Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
*
|
||||
* <p>Note the constructor does not bind the socket, {@link #bind()} method should be called for
|
||||
* binding the socket.
|
||||
*
|
||||
* @param port the port on which channel will be bound to accept incoming connection requests.
|
||||
*
|
||||
* @param port the port on which channel will be bound to accept incoming connection requests.
|
||||
* @param handler the handler that will handle incoming requests on this channel.
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@ -68,6 +67,8 @@ public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server socket channel.
|
||||
*
|
||||
* @return the underlying {@link ServerSocketChannel}.
|
||||
*/
|
||||
@Override
|
||||
@ -94,7 +95,7 @@ public class NioServerSocketChannel extends AbstractNioChannel {
|
||||
|
||||
/**
|
||||
* Binds TCP socket on the provided <code>port</code>.
|
||||
*
|
||||
*
|
||||
* @throws IOException if any I/O error occurs.
|
||||
*/
|
||||
@Override
|
||||
|
@ -29,18 +29,16 @@ import java.nio.channels.SelectionKey;
|
||||
* Dispatches the events in the context of caller thread. This implementation is a good fit for
|
||||
* small applications where there are limited clients. Using this implementation limits the
|
||||
* scalability because the I/O thread performs the application specific processing.
|
||||
*
|
||||
* <p>
|
||||
* For better performance use {@link ThreadPoolDispatcher}.
|
||||
*
|
||||
*
|
||||
* <p>For better performance use {@link ThreadPoolDispatcher}.
|
||||
*
|
||||
* @see ThreadPoolDispatcher
|
||||
*/
|
||||
public class SameThreadDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Dispatches the read event in the context of caller thread. <br>
|
||||
* Note this is a blocking call. It returns only after the associated handler has handled the read
|
||||
* event.
|
||||
* Dispatches the read event in the context of caller thread. <br> Note this is a blocking call.
|
||||
* It returns only after the associated handler has handled the read event.
|
||||
*/
|
||||
@Override
|
||||
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
|
@ -39,7 +39,7 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Creates a pooled dispatcher with tunable pool size.
|
||||
*
|
||||
*
|
||||
* @param poolSize number of pooled threads
|
||||
*/
|
||||
public ThreadPoolDispatcher(int poolSize) {
|
||||
@ -48,9 +48,8 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Submits the work of dispatching the read event to worker pool, where it gets picked up by
|
||||
* worker threads. <br>
|
||||
* Note that this is a non-blocking call and returns immediately. It is not guaranteed that the
|
||||
* event has been handled by associated handler.
|
||||
* worker threads. <br> Note that this is a non-blocking call and returns immediately. It is not
|
||||
* guaranteed that the event has been handled by associated handler.
|
||||
*/
|
||||
@Override
|
||||
public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) {
|
||||
@ -59,7 +58,7 @@ public class ThreadPoolDispatcher implements Dispatcher {
|
||||
|
||||
/**
|
||||
* Stops the pool of workers.
|
||||
*
|
||||
*
|
||||
* @throws InterruptedException if interrupted while stopping pool of workers.
|
||||
*/
|
||||
@Override
|
||||
|
@ -28,26 +28,23 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* In a multiple thread applications, the threads may try to synchronize the shared resources
|
||||
* regardless of read or write operation. It leads to a low performance especially in a "read more
|
||||
* write less" system as indeed the read operations are thread-safe to another read operation.
|
||||
* <p>
|
||||
* Reader writer lock is a synchronization primitive that try to resolve this problem. This pattern
|
||||
* allows concurrent access for read-only operations, while write operations require exclusive
|
||||
* access. This means that multiple threads can read the data in parallel but an exclusive lock is
|
||||
* needed for writing or modifying data. When a writer is writing the data, all other writers or
|
||||
* readers will be blocked until the writer is finished writing.
|
||||
*
|
||||
* <p>
|
||||
* This example use two mutex to demonstrate the concurrent access of multiple readers and writers.
|
||||
*
|
||||
*
|
||||
*
|
||||
* <p>Reader writer lock is a synchronization primitive that try to resolve this problem. This
|
||||
* pattern allows concurrent access for read-only operations, while write operations require
|
||||
* exclusive access. This means that multiple threads can read the data in parallel but an exclusive
|
||||
* lock is needed for writing or modifying data. When a writer is writing the data, all other
|
||||
* writers or readers will be blocked until the writer is finished writing.
|
||||
*
|
||||
* <p>This example use two mutex to demonstrate the concurrent access of multiple readers and
|
||||
* writers.
|
||||
*
|
||||
* @author hongshuwei@gmail.com
|
||||
*/
|
||||
public class App {
|
||||
@ -55,27 +52,27 @@ public class App {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
*
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
ExecutorService executeService = Executors.newFixedThreadPool(10);
|
||||
ReaderWriterLock lock = new ReaderWriterLock();
|
||||
|
||||
|
||||
// Start writers
|
||||
IntStream.range(0, 5)
|
||||
.forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock(),
|
||||
.forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock(),
|
||||
ThreadLocalRandom.current().nextLong(5000))));
|
||||
LOGGER.info("Writers added...");
|
||||
|
||||
// Start readers
|
||||
IntStream.range(0, 5)
|
||||
.forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(),
|
||||
.forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(),
|
||||
ThreadLocalRandom.current().nextLong(10))));
|
||||
LOGGER.info("Readers added...");
|
||||
|
||||
|
||||
try {
|
||||
Thread.sleep(5000L);
|
||||
} catch (InterruptedException e) {
|
||||
@ -85,11 +82,10 @@ public class App {
|
||||
|
||||
// Start readers
|
||||
IntStream.range(6, 10)
|
||||
.forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(),
|
||||
.forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(),
|
||||
ThreadLocalRandom.current().nextLong(10))));
|
||||
LOGGER.info("More readers added...");
|
||||
|
||||
|
||||
|
||||
|
||||
// In the system console, it can see that the read operations are executed concurrently while
|
||||
// write operations are exclusive.
|
||||
|
@ -24,12 +24,11 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Reader class, read when it acquired the read lock
|
||||
* Reader class, read when it acquired the read lock.
|
||||
*/
|
||||
public class Reader implements Runnable {
|
||||
|
||||
@ -38,14 +37,14 @@ public class Reader implements Runnable {
|
||||
private Lock readLock;
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
private long readingTime;
|
||||
|
||||
/**
|
||||
* Create new Reader
|
||||
*
|
||||
* @param name - Name of the thread owning the reader
|
||||
* @param readLock - Lock for this reader
|
||||
* Create new Reader.
|
||||
*
|
||||
* @param name - Name of the thread owning the reader
|
||||
* @param readLock - Lock for this reader
|
||||
* @param readingTime - amount of time (in milliseconds) for this reader to engage reading
|
||||
*/
|
||||
public Reader(String name, Lock readLock, long readingTime) {
|
||||
@ -53,11 +52,11 @@ public class Reader implements Runnable {
|
||||
this.readLock = readLock;
|
||||
this.readingTime = readingTime;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new Reader who reads for 250ms
|
||||
*
|
||||
* @param name - Name of the thread owning the reader
|
||||
* Create new Reader who reads for 250ms.
|
||||
*
|
||||
* @param name - Name of the thread owning the reader
|
||||
* @param readLock - Lock for this reader
|
||||
*/
|
||||
public Reader(String name, Lock readLock) {
|
||||
@ -78,8 +77,7 @@ public class Reader implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate the read operation
|
||||
*
|
||||
* Simulate the read operation.
|
||||
*/
|
||||
public void read() throws InterruptedException {
|
||||
LOGGER.info("{} begin", name);
|
||||
|
@ -29,18 +29,17 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class responsible for control the access for reader or writer
|
||||
*
|
||||
* Allows multiple readers to hold the lock at same time, but if any writer holds the lock then readers wait. If reader
|
||||
* holds the lock then writer waits. This lock is not fair.
|
||||
*
|
||||
* <p>Allows multiple readers to hold the lock at same time, but if any writer holds the lock then
|
||||
* readers wait. If reader holds the lock then writer waits. This lock is not fair.
|
||||
*/
|
||||
public class ReaderWriterLock implements ReadWriteLock {
|
||||
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReaderWriterLock.class);
|
||||
|
||||
|
||||
@ -50,13 +49,13 @@ public class ReaderWriterLock implements ReadWriteLock {
|
||||
|
||||
/**
|
||||
* Global mutex is used to indicate that whether reader or writer gets the lock in the moment.
|
||||
* <p>
|
||||
* 1. When it contains the reference of {@link #readerLock}, it means that the lock is acquired by the reader, another
|
||||
* reader can also do the read operation concurrently. <br>
|
||||
* 2. When it contains the reference of reference of {@link #writerLock}, it means that the lock is acquired by the
|
||||
* writer exclusively, no more reader or writer can get the lock.
|
||||
* <p>
|
||||
* This is the most important field in this class to control the access for reader/writer.
|
||||
*
|
||||
* <p>1. When it contains the reference of {@link #readerLock}, it means that the lock is
|
||||
* acquired by the reader, another reader can also do the read operation concurrently. <br> 2.
|
||||
* When it contains the reference of reference of {@link #writerLock}, it means that the lock is
|
||||
* acquired by the writer exclusively, no more reader or writer can get the lock.
|
||||
*
|
||||
* <p>This is the most important field in this class to control the access for reader/writer.
|
||||
*/
|
||||
private Set<Object> globalMutex = new HashSet<>();
|
||||
|
||||
@ -74,22 +73,21 @@ public class ReaderWriterLock implements ReadWriteLock {
|
||||
}
|
||||
|
||||
/**
|
||||
* return true when globalMutex hold the reference of writerLock
|
||||
* return true when globalMutex hold the reference of writerLock.
|
||||
*/
|
||||
private boolean doesWriterOwnThisLock() {
|
||||
return globalMutex.contains(writerLock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nobody get the lock when globalMutex contains nothing
|
||||
*
|
||||
* Nobody get the lock when globalMutex contains nothing.
|
||||
*/
|
||||
private boolean isLockFree() {
|
||||
return globalMutex.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reader Lock, can be access for more than one reader concurrently if no writer get the lock
|
||||
* Reader Lock, can be access for more than one reader concurrently if no writer get the lock.
|
||||
*/
|
||||
private class ReadLock implements Lock {
|
||||
|
||||
@ -104,8 +102,8 @@ public class ReaderWriterLock implements ReadWriteLock {
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no writers currently
|
||||
* owns the lock.
|
||||
* Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no
|
||||
* writers currently owns the lock.
|
||||
*/
|
||||
private void acquireForReaders() {
|
||||
// Try to get the globalMutex lock for the first reader
|
||||
@ -116,7 +114,8 @@ public class ReaderWriterLock implements ReadWriteLock {
|
||||
try {
|
||||
globalMutex.wait();
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.info("InterruptedException while waiting for globalMutex in acquireForReaders", e);
|
||||
LOGGER
|
||||
.info("InterruptedException while waiting for globalMutex in acquireForReaders", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
@ -165,7 +164,7 @@ public class ReaderWriterLock implements ReadWriteLock {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writer Lock, can only be accessed by one writer concurrently
|
||||
* Writer Lock, can only be accessed by one writer concurrently.
|
||||
*/
|
||||
private class WriteLock implements Lock {
|
||||
|
||||
|
@ -24,12 +24,11 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Writer class, write when it acquired the write lock
|
||||
* Writer class, write when it acquired the write lock.
|
||||
*/
|
||||
public class Writer implements Runnable {
|
||||
|
||||
@ -38,24 +37,24 @@ public class Writer implements Runnable {
|
||||
private Lock writeLock;
|
||||
|
||||
private String name;
|
||||
|
||||
|
||||
private long writingTime;
|
||||
|
||||
/**
|
||||
* Create new Writer who writes for 250ms
|
||||
*
|
||||
* @param name - Name of the thread owning the writer
|
||||
* Create new Writer who writes for 250ms.
|
||||
*
|
||||
* @param name - Name of the thread owning the writer
|
||||
* @param writeLock - Lock for this writer
|
||||
*/
|
||||
public Writer(String name, Lock writeLock) {
|
||||
this(name, writeLock, 250L);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create new Writer
|
||||
*
|
||||
* @param name - Name of the thread owning the writer
|
||||
* @param writeLock - Lock for this writer
|
||||
* Create new Writer.
|
||||
*
|
||||
* @param name - Name of the thread owning the writer
|
||||
* @param writeLock - Lock for this writer
|
||||
* @param writingTime - amount of time (in milliseconds) for this reader to engage writing
|
||||
*/
|
||||
public Writer(String name, Lock writeLock, long writingTime) {
|
||||
@ -77,9 +76,9 @@ public class Writer implements Runnable {
|
||||
writeLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simulate the write operation
|
||||
* Simulate the write operation.
|
||||
*/
|
||||
public void write() throws InterruptedException {
|
||||
LOGGER.info("{} begin", name);
|
||||
|
@ -25,7 +25,6 @@ package com.iluwatar.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
@ -38,8 +37,8 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
* query construction code is concentrated. This becomes more important when there are a large
|
||||
* number of domain classes or heavy querying. In these cases particularly, adding this layer helps
|
||||
* minimize duplicate query logic.
|
||||
* <p>
|
||||
* In this example we utilize Spring Data to automatically generate a repository for us from the
|
||||
*
|
||||
* <p>In this example we utilize Spring Data to automatically generate a repository for us from the
|
||||
* {@link Person} domain object. Using the {@link PersonRepository} we perform CRUD operations on
|
||||
* the entity, moreover, the query by {@link org.springframework.data.jpa.domain.Specification} are
|
||||
* also performed. Underneath we have configured in-memory H2 database for which schema is created
|
||||
@ -50,10 +49,9 @@ public class App {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
*
|
||||
* @param args
|
||||
* command line args
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
@ -107,7 +105,7 @@ public class App {
|
||||
}
|
||||
|
||||
repository.deleteAll();
|
||||
|
||||
|
||||
context.close();
|
||||
|
||||
}
|
||||
|
@ -27,9 +27,7 @@ import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.dbcp.BasicDataSource;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.slf4j.Logger;
|
||||
@ -42,9 +40,7 @@ import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
/**
|
||||
* This is the same example as in {@link App} but with annotations based
|
||||
* configuration for Spring.
|
||||
*
|
||||
* This is the same example as in {@link App} but with annotations based configuration for Spring.
|
||||
*/
|
||||
@EnableJpaRepositories
|
||||
@SpringBootConfiguration
|
||||
@ -53,8 +49,8 @@ public class AppConfig {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AppConfig.class);
|
||||
|
||||
/**
|
||||
* Creation of H2 db
|
||||
*
|
||||
* Creation of H2 db.
|
||||
*
|
||||
* @return A new Instance of DataSource
|
||||
*/
|
||||
@Bean(destroyMethod = "close")
|
||||
@ -68,11 +64,12 @@ public class AppConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to create a especific instance of Entity Manager
|
||||
* Factory to create a especific instance of Entity Manager.
|
||||
*/
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
|
||||
LocalContainerEntityManagerFactoryBean entityManager =
|
||||
new LocalContainerEntityManagerFactoryBean();
|
||||
entityManager.setDataSource(dataSource());
|
||||
entityManager.setPackagesToScan("com.iluwatar");
|
||||
entityManager.setPersistenceProvider(new HibernatePersistenceProvider());
|
||||
@ -82,7 +79,7 @@ public class AppConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for Jpa
|
||||
* Properties for Jpa.
|
||||
*/
|
||||
private static Properties jpaProperties() {
|
||||
Properties properties = new Properties();
|
||||
@ -92,7 +89,7 @@ public class AppConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction manager
|
||||
* Get transaction manager.
|
||||
*/
|
||||
@Bean
|
||||
public JpaTransactionManager transactionManager() throws SQLException {
|
||||
@ -102,10 +99,9 @@ public class AppConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
*
|
||||
* @param args
|
||||
* command line args
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
@ -28,9 +28,7 @@ import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
*
|
||||
* Person entity
|
||||
*
|
||||
* Person entity.
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
@ -47,7 +45,7 @@ public class Person {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* Constructor.
|
||||
*/
|
||||
public Person(String name, String surname, int age) {
|
||||
this.name = name;
|
||||
|
@ -28,9 +28,7 @@ import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
*
|
||||
* Person repository
|
||||
*
|
||||
* Person repository.
|
||||
*/
|
||||
@Repository
|
||||
public interface PersonRepository
|
||||
|
@ -27,16 +27,15 @@ import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
/**
|
||||
* Helper class, includes vary Specification as the abstraction of sql query criteria
|
||||
* Helper class, includes vary Specification as the abstraction of sql query criteria.
|
||||
*/
|
||||
public class PersonSpecifications {
|
||||
|
||||
/**
|
||||
* Specifications stating the Between (From - To) Age Specification
|
||||
* Specifications stating the Between (From - To) Age Specification.
|
||||
*/
|
||||
public static class AgeBetweenSpec implements Specification<Person> {
|
||||
|
||||
@ -59,8 +58,7 @@ public class PersonSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* Name specification
|
||||
*
|
||||
* Name specification.
|
||||
*/
|
||||
public static class NameEqualSpec implements Specification<Person> {
|
||||
|
||||
@ -71,7 +69,7 @@ public class PersonSpecifications {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get predicate
|
||||
* Get predicate.
|
||||
*/
|
||||
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
|
||||
|
||||
|
@ -27,31 +27,30 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Resource Acquisition Is Initialization pattern was developed for exception safe resource
|
||||
* management by C++ creator Bjarne Stroustrup.
|
||||
* <p>
|
||||
* In RAII resource is tied to object lifetime: resource allocation is done during object creation
|
||||
* while resource deallocation is done during object destruction.
|
||||
* <p>
|
||||
* In Java RAII is achieved with try-with-resources statement and interfaces {@link java.io.Closeable} and
|
||||
* {@link AutoCloseable}. The try-with-resources statement ensures that each resource is closed at
|
||||
* the end of the statement. Any object that implements {@link java.lang.AutoCloseable}, which
|
||||
* includes all objects which implement {@link java.io.Closeable}, can be used as a resource.
|
||||
*
|
||||
* In this example, {@link SlidingDoor} implements {@link AutoCloseable} and {@link TreasureChest}
|
||||
* implements {@link java.io.Closeable}. Running the example, we can observe that both resources are
|
||||
* automatically closed.
|
||||
* <p>
|
||||
* http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html
|
||||
* <p>In RAII resource is tied to object lifetime: resource allocation is done during object
|
||||
* creation while resource deallocation is done during object destruction.
|
||||
*
|
||||
* <p>In Java RAII is achieved with try-with-resources statement and interfaces {@link
|
||||
* java.io.Closeable} and {@link AutoCloseable}. The try-with-resources statement ensures that each
|
||||
* resource is closed at the end of the statement. Any object that implements {@link
|
||||
* java.lang.AutoCloseable}, which includes all objects which implement {@link java.io.Closeable},
|
||||
* can be used as a resource.
|
||||
*
|
||||
* <p>In this example, {@link SlidingDoor} implements {@link AutoCloseable} and {@link
|
||||
* TreasureChest} implements {@link java.io.Closeable}. Running the example, we can observe that
|
||||
* both resources are automatically closed.
|
||||
*
|
||||
* <p>http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
* Program entry point.
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
|
@ -27,9 +27,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* SlidingDoor resource
|
||||
*
|
||||
* SlidingDoor resource.
|
||||
*/
|
||||
public class SlidingDoor implements AutoCloseable {
|
||||
|
||||
|
@ -23,16 +23,13 @@
|
||||
|
||||
package com.iluwatar.resource.acquisition.is.initialization;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* TreasureChest resource
|
||||
*
|
||||
* TreasureChest resource.
|
||||
*/
|
||||
public class TreasureChest implements Closeable {
|
||||
|
||||
|
@ -27,34 +27,35 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The <em>Retry</em> pattern enables applications to handle potentially recoverable failures from
|
||||
* the environment if the business requirements and nature of the failures allow it. By retrying
|
||||
* The <em>Retry</em> pattern enables applications to handle potentially recoverable failures from
|
||||
* the environment if the business requirements and nature of the failures allow it. By retrying
|
||||
* failed operations on external dependencies, the application may maintain stability and minimize
|
||||
* negative impact on the user experience.
|
||||
* <p>
|
||||
* In our example, we have the {@link BusinessOperation} interface as an abstraction over
|
||||
* all operations that our application performs involving remote systems. The calling code should
|
||||
* remain decoupled from implementations.
|
||||
* <p>
|
||||
* {@link FindCustomer} is a business operation that looks up a customer's record and returns
|
||||
* its ID. Imagine its job is performed by looking up the customer in our local database and
|
||||
* returning its ID. We can pass {@link CustomerNotFoundException} as one of its
|
||||
* {@link FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...)
|
||||
* constructor parameters} in order to simulate not finding the customer.
|
||||
* <p>
|
||||
* Imagine that, lately, this operation has experienced intermittent failures due to some weird
|
||||
* corruption and/or locking in the data. After retrying a few times the customer is found. The
|
||||
* database is still, however, expected to always be available. While a definitive solution is
|
||||
* found to the problem, our engineers advise us to retry the operation a set number
|
||||
* of times with a set delay between retries, although not too many retries otherwise the end user
|
||||
* will be left waiting for a long time, while delays that are too short will not allow the database
|
||||
* to recover from the load.
|
||||
* <p>
|
||||
* To keep the calling code as decoupled as possible from this workaround, we have implemented the
|
||||
* retry mechanism as a {@link BusinessOperation} named {@link Retry}.
|
||||
*
|
||||
*
|
||||
* <p>In our example, we have the {@link BusinessOperation} interface as an abstraction over all
|
||||
* operations that our application performs involving remote systems. The calling code should remain
|
||||
* decoupled from implementations.
|
||||
*
|
||||
* <p>{@link FindCustomer} is a business operation that looks up a customer's record and returns
|
||||
* its ID. Imagine its job is performed by looking up the customer in our local database and
|
||||
* returning its ID. We can pass {@link CustomerNotFoundException} as one of its {@link
|
||||
* FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) constructor
|
||||
* parameters} in order to simulate not finding the customer.
|
||||
*
|
||||
* <p>Imagine that, lately, this operation has experienced intermittent failures due to some weird
|
||||
* corruption and/or locking in the data. After retrying a few times the customer is found. The
|
||||
* database is still, however, expected to always be available. While a definitive solution is
|
||||
* found to the problem, our engineers advise us to retry the operation a set number of times with a
|
||||
* set delay between retries, although not too many retries otherwise the end user will be left
|
||||
* waiting for a long time, while delays that are too short will not allow the database to recover
|
||||
* from the load.
|
||||
*
|
||||
* <p>To keep the calling code as decoupled as possible from this workaround, we have implemented
|
||||
* the retry mechanism as a {@link BusinessOperation} named {@link Retry}.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
* @see <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry">Retry pattern (Microsoft Azure Docs)</a>
|
||||
* @see <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/retry">Retry pattern
|
||||
* (Microsoft Azure Docs)</a>
|
||||
*/
|
||||
public final class App {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(App.class);
|
||||
@ -62,7 +63,7 @@ public final class App {
|
||||
|
||||
/**
|
||||
* Entry point.
|
||||
*
|
||||
*
|
||||
* @param args not used
|
||||
* @throws Exception not expected
|
||||
*/
|
||||
@ -99,22 +100,22 @@ public final class App {
|
||||
final String customerId = op.perform();
|
||||
LOG.info(String.format(
|
||||
"However, retrying the operation while ignoring a recoverable error will eventually yield "
|
||||
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
|
||||
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
private static void errorWithRetryExponentialBackoff() throws Exception {
|
||||
final RetryExponentialBackoff<String> retry = new RetryExponentialBackoff<>(
|
||||
new FindCustomer("123", new CustomerNotFoundException("not found")),
|
||||
6, //6 attempts
|
||||
30000, //30 s max delay between attempts
|
||||
e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())
|
||||
);
|
||||
);
|
||||
op = retry;
|
||||
final String customerId = op.perform();
|
||||
LOG.info(String.format(
|
||||
"However, retrying the operation while ignoring a recoverable error will eventually yield "
|
||||
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
|
||||
));
|
||||
"However, retrying the operation while ignoring a recoverable error will eventually yield "
|
||||
+ "the result %s after a number of attempts %s", customerId, retry.attempts()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -24,10 +24,10 @@
|
||||
package com.iluwatar.retry;
|
||||
|
||||
/**
|
||||
* The top-most type in our exception hierarchy that signifies that an unexpected error
|
||||
* condition occurred. Its use is reserved as a "catch-all" for cases where no other subtype
|
||||
* captures the specificity of the error condition in question. Calling code is not expected to
|
||||
* be able to handle this error and should be reported to the maintainers immediately.
|
||||
* The top-most type in our exception hierarchy that signifies that an unexpected error condition
|
||||
* occurred. Its use is reserved as a "catch-all" for cases where no other subtype captures the
|
||||
* specificity of the error condition in question. Calling code is not expected to be able to handle
|
||||
* this error and should be reported to the maintainers immediately.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
*/
|
||||
@ -35,8 +35,8 @@ public class BusinessException extends Exception {
|
||||
private static final long serialVersionUID = 6235833142062144336L;
|
||||
|
||||
/**
|
||||
* Ctor
|
||||
*
|
||||
* Ctor.
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public BusinessException(String message) {
|
||||
|
@ -26,18 +26,18 @@ package com.iluwatar.retry;
|
||||
/**
|
||||
* Performs some business operation.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
* @param <T> the return type
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BusinessOperation<T> {
|
||||
/**
|
||||
* Performs some business operation, returning a value {@code T} if successful, otherwise throwing
|
||||
* an exception if an error occurs.
|
||||
*
|
||||
*
|
||||
* @return the return value
|
||||
* @throws BusinessException if the operation fails. Implementations are allowed to throw more
|
||||
* specific subtypes depending on the error conditions
|
||||
* specific subtypes depending on the error conditions
|
||||
*/
|
||||
T perform() throws BusinessException;
|
||||
}
|
||||
|
@ -24,10 +24,11 @@
|
||||
package com.iluwatar.retry;
|
||||
|
||||
/**
|
||||
* Indicates that the customer was not found.
|
||||
* <p>
|
||||
* The severity of this error is bounded by its context: was the search for the customer triggered
|
||||
* by an input from some end user, or were the search parameters pulled from your database?
|
||||
* Indicates that the customer was not found.
|
||||
*
|
||||
* <p>The severity of this error is bounded by its context: was the search for the customer
|
||||
* triggered by an input from some end user, or were the search parameters pulled from your
|
||||
* database?
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
*/
|
||||
@ -36,7 +37,7 @@ public final class CustomerNotFoundException extends BusinessException {
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public CustomerNotFoundException(String message) {
|
||||
|
@ -33,7 +33,7 @@ public final class DatabaseNotAvailableException extends BusinessException {
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
*
|
||||
* @param message the error message
|
||||
*/
|
||||
public DatabaseNotAvailableException(String message) {
|
||||
|
@ -29,9 +29,9 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Finds a customer, returning its ID from our records.
|
||||
* <p>
|
||||
* This is an imaginary operation that, for some imagined input, returns the ID for a customer.
|
||||
* However, this is a "flaky" operation that is supposed to fail intermittently, but for the
|
||||
*
|
||||
* <p>This is an imaginary operation that, for some imagined input, returns the ID for a customer.
|
||||
* However, this is a "flaky" operation that is supposed to fail intermittently, but for the
|
||||
* purposes of this example it fails in a programmed way depending on the constructor parameters.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
@ -42,15 +42,15 @@ public final class FindCustomer implements BusinessOperation<String> {
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
*
|
||||
* @param customerId the final result of the remote operation
|
||||
* @param errors the errors to throw before returning {@code customerId}
|
||||
* @param errors the errors to throw before returning {@code customerId}
|
||||
*/
|
||||
public FindCustomer(String customerId, BusinessException... errors) {
|
||||
this.customerId = customerId;
|
||||
this.errors = new ArrayDeque<>(List.of(errors));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String perform() throws BusinessException {
|
||||
if (!this.errors.isEmpty()) {
|
||||
|
@ -33,8 +33,8 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Decorates {@link BusinessOperation business operation} with "retry" capabilities.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
* @param <T> the remote op's return type
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
*/
|
||||
public final class Retry<T> implements BusinessOperation<T> {
|
||||
private final BusinessOperation<T> op;
|
||||
@ -46,18 +46,18 @@ public final class Retry<T> implements BusinessOperation<T> {
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
* @param op the {@link BusinessOperation} to retry
|
||||
*
|
||||
* @param op the {@link BusinessOperation} to retry
|
||||
* @param maxAttempts number of times to retry
|
||||
* @param delay delay (in milliseconds) between attempts
|
||||
* @param delay delay (in milliseconds) between attempts
|
||||
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
|
||||
* will be ignored if no tests are given
|
||||
* will be ignored if no tests are given
|
||||
*/
|
||||
@SafeVarargs
|
||||
public Retry(
|
||||
BusinessOperation<T> op,
|
||||
int maxAttempts,
|
||||
long delay,
|
||||
BusinessOperation<T> op,
|
||||
int maxAttempts,
|
||||
long delay,
|
||||
Predicate<Exception>... ignoreTests
|
||||
) {
|
||||
this.op = op;
|
||||
@ -70,7 +70,7 @@ public final class Retry<T> implements BusinessOperation<T> {
|
||||
|
||||
/**
|
||||
* The errors encountered while retrying, in the encounter order.
|
||||
*
|
||||
*
|
||||
* @return the errors encountered while retrying
|
||||
*/
|
||||
public List<Exception> errors() {
|
||||
@ -79,7 +79,7 @@ public final class Retry<T> implements BusinessOperation<T> {
|
||||
|
||||
/**
|
||||
* The number of retries performed.
|
||||
*
|
||||
*
|
||||
* @return the number of retries performed
|
||||
*/
|
||||
public int attempts() {
|
||||
@ -93,7 +93,7 @@ public final class Retry<T> implements BusinessOperation<T> {
|
||||
return this.op.perform();
|
||||
} catch (BusinessException e) {
|
||||
this.errors.add(e);
|
||||
|
||||
|
||||
if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) {
|
||||
throw e;
|
||||
}
|
||||
@ -104,7 +104,6 @@ public final class Retry<T> implements BusinessOperation<T> {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,8 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Decorates {@link BusinessOperation business operation} with "retry" capabilities.
|
||||
*
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
* @param <T> the remote op's return type
|
||||
* @author George Aristy (george.aristy@gmail.com)
|
||||
*/
|
||||
public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
|
||||
private static final Random RANDOM = new Random();
|
||||
@ -46,20 +46,20 @@ public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
|
||||
private final Predicate<Exception> test;
|
||||
private final List<Exception> errors;
|
||||
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
* @param op the {@link BusinessOperation} to retry
|
||||
* @param maxAttempts number of times to retry
|
||||
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
|
||||
* will be ignored if no tests are given
|
||||
*/
|
||||
/**
|
||||
* Ctor.
|
||||
*
|
||||
* @param op the {@link BusinessOperation} to retry
|
||||
* @param maxAttempts number of times to retry
|
||||
* @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions
|
||||
* will be ignored if no tests are given
|
||||
*/
|
||||
@SafeVarargs
|
||||
public RetryExponentialBackoff(
|
||||
BusinessOperation<T> op,
|
||||
int maxAttempts,
|
||||
long maxDelay,
|
||||
Predicate<Exception>... ignoreTests
|
||||
BusinessOperation<T> op,
|
||||
int maxAttempts,
|
||||
long maxDelay,
|
||||
Predicate<Exception>... ignoreTests
|
||||
) {
|
||||
this.op = op;
|
||||
this.maxAttempts = maxAttempts;
|
||||
@ -69,20 +69,20 @@ public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
|
||||
this.errors = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* The errors encountered while retrying, in the encounter order.
|
||||
*
|
||||
* @return the errors encountered while retrying
|
||||
*/
|
||||
/**
|
||||
* The errors encountered while retrying, in the encounter order.
|
||||
*
|
||||
* @return the errors encountered while retrying
|
||||
*/
|
||||
public List<Exception> errors() {
|
||||
return Collections.unmodifiableList(this.errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of retries performed.
|
||||
*
|
||||
* @return the number of retries performed
|
||||
*/
|
||||
/**
|
||||
* The number of retries performed.
|
||||
*
|
||||
* @return the number of retries performed
|
||||
*/
|
||||
public int attempts() {
|
||||
return this.attempts.intValue();
|
||||
}
|
||||
@ -107,8 +107,7 @@ public final class RetryExponentialBackoff<T> implements BusinessOperation<T> {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user