added Poison Pill idiom
This commit is contained in:
parent
6366041781
commit
dc0f578f8b
@ -71,7 +71,7 @@ A programming idiom is a means of expressing a recurring construct in one or mor
|
||||
|
||||
* [Execute Around](#execute-around)
|
||||
* [Double Checked Locking](#double-checked-locking)
|
||||
|
||||
* [Poison Pill](#poison-pill)
|
||||
|
||||
|
||||
## <a name="abstract-factory">Abstract Factory</a> [↑](#list-of-design-patterns)
|
||||
@ -475,6 +475,13 @@ A programming idiom is a means of expressing a recurring construct in one or mor
|
||||
**Real world examples:**
|
||||
* [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) prototype inheritance
|
||||
|
||||
## <a name="poison-pill">Poison Pill</a> [↑](#list-of-design-patterns)
|
||||
**Intent:** Poison Pill is known predefined data item that allows to provide graceful shutdown for separate distributed consumption process.
|
||||
|
||||

|
||||
|
||||
**Applicability:** Use the Poison Pill idiom when
|
||||
* need to send signal from one thread/process to another to terminate
|
||||
|
||||
# Frequently asked questions
|
||||
|
||||
|
BIN
poison-pill/etc/poison-pill.png
Normal file
BIN
poison-pill/etc/poison-pill.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
92
poison-pill/etc/poison-pill.ucls
Normal file
92
poison-pill/etc/poison-pill.ucls
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
||||
<class id="1" language="java" name="com.iluwatar.Producer" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/Producer.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="153" width="182" x="158" y="209"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<interface id="2" language="java" name="com.iluwatar.MQPublishPoint" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/MQPublishPoint.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="453" y="283"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<class id="3" language="java" name="com.iluwatar.SimpleMessageQueue" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/SimpleMessageQueue.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="627" y="115"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<interface id="4" language="java" name="com.iluwatar.MessageQueue" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/MessageQueue.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="627" y="283"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<interface id="5" language="java" name="com.iluwatar.MQSubscribePoint" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/MQSubscribePoint.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="821" y="282"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</interface>
|
||||
<class id="6" language="java" name="com.iluwatar.Consumer" project="poison-pill"
|
||||
file="/poison-pill/src/main/java/com/iluwatar/Consumer.java" binary="false" corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="1055" y="282"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<generalization id="7">
|
||||
<end type="SOURCE" refId="4"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</generalization>
|
||||
<realization id="8">
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="4"/>
|
||||
</realization>
|
||||
<generalization id="9">
|
||||
<end type="SOURCE" refId="4"/>
|
||||
<end type="TARGET" refId="5"/>
|
||||
</generalization>
|
||||
<association id="10">
|
||||
<end type="SOURCE" refId="6" navigable="false">
|
||||
<attribute id="11" name="queue"/>
|
||||
<multiplicity id="12" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="5" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<association id="13">
|
||||
<end type="SOURCE" refId="1" navigable="false">
|
||||
<attribute id="14" name="queue"/>
|
||||
<multiplicity id="15" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="2" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</classifier-display>
|
||||
<association-display labels="true" multiplicity="true"/>
|
||||
</class-diagram>
|
18
poison-pill/pom.xml
Normal file
18
poison-pill/pom.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poison-pill</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
37
poison-pill/src/main/java/com/iluwatar/App.java
Normal file
37
poison-pill/src/main/java/com/iluwatar/App.java
Normal file
@ -0,0 +1,37 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* One of possible approaches to terminate Producer-Consumer pattern is using PoisonPill idiom.
|
||||
* If you use PoisonPill as termination signal then Producer is responsible to notify Consumer that exchange is over
|
||||
* and reject any further messages. Consumer receiving PoisonPill will stop to read messages from queue.
|
||||
* You also must ensure that PoisonPill will be last message that will be read from queue (if you have
|
||||
* prioritized queue than this can be tricky).
|
||||
* In simple cases as PoisonPill can be used just null-reference, but holding unique separate shared
|
||||
* object-marker (with name "Poison" or "PoisonPill") is more clear and self describing.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
public static void main(String[] args) {
|
||||
MessageQueue queue = new SimpleMessageQueue(10000);
|
||||
|
||||
final Producer producer = new Producer("PRODUCER_1", queue);
|
||||
final Consumer consumer = new Consumer("CONSUMER_1", queue);
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
consumer.consume();
|
||||
}
|
||||
}.start();
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
producer.send("hand shake");
|
||||
producer.send("some very important information");
|
||||
producer.send("bye!");
|
||||
producer.stop();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
38
poison-pill/src/main/java/com/iluwatar/Consumer.java
Normal file
38
poison-pill/src/main/java/com/iluwatar/Consumer.java
Normal file
@ -0,0 +1,38 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import com.iluwatar.Message.Headers;
|
||||
|
||||
/**
|
||||
* Class responsible for receiving and handling submitted to the queue messages
|
||||
*/
|
||||
public class Consumer {
|
||||
|
||||
private final MQSubscribePoint queue;
|
||||
private final String name;
|
||||
|
||||
public Consumer(String name, MQSubscribePoint queue) {
|
||||
this.name = name;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public void consume() {
|
||||
while (true) {
|
||||
Message msg;
|
||||
try {
|
||||
msg = queue.take();
|
||||
if (msg == Message.POISON_PILL) {
|
||||
System.out.println(String.format("Consumer %s receive request to terminate.", name));
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
System.err.println(e);
|
||||
return;
|
||||
}
|
||||
|
||||
String sender = msg.getHeader(Headers.SENDER);
|
||||
String body = msg.getBody();
|
||||
System.out.println(String.format("Message [%s] from [%s] received by [%s]", body, sender, name));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* Endpoint to publish {@link Message} to queue
|
||||
*/
|
||||
public interface MQPublishPoint {
|
||||
|
||||
public void put(Message msg) throws InterruptedException;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* Endpoint to retrieve {@link Message} from queue
|
||||
*/
|
||||
public interface MQSubscribePoint {
|
||||
|
||||
public Message take() throws InterruptedException;
|
||||
}
|
52
poison-pill/src/main/java/com/iluwatar/Message.java
Normal file
52
poison-pill/src/main/java/com/iluwatar/Message.java
Normal file
@ -0,0 +1,52 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface that implements the Message pattern and represents an inbound or outbound message as part of an {@link Producer}-{@link Consumer} exchange.
|
||||
*/
|
||||
public interface Message {
|
||||
|
||||
public static final Message POISON_PILL = new Message() {
|
||||
|
||||
@Override
|
||||
public void addHeader(Headers header, String value) {
|
||||
throw poison();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(Headers header) {
|
||||
throw poison();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Headers, String> getHeaders() {
|
||||
throw poison();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBody(String body) {
|
||||
throw poison();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
throw poison();
|
||||
}
|
||||
|
||||
private RuntimeException poison() {
|
||||
return new UnsupportedOperationException("Poison");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public enum Headers {
|
||||
DATE, SENDER
|
||||
}
|
||||
|
||||
public void addHeader(Headers header, String value);
|
||||
public String getHeader(Headers header);
|
||||
public Map<Headers, String> getHeaders();
|
||||
public void setBody(String body);
|
||||
public String getBody();
|
||||
}
|
8
poison-pill/src/main/java/com/iluwatar/MessageQueue.java
Normal file
8
poison-pill/src/main/java/com/iluwatar/MessageQueue.java
Normal file
@ -0,0 +1,8 @@
|
||||
package com.iluwatar;
|
||||
|
||||
/**
|
||||
* Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer}
|
||||
*/
|
||||
public interface MessageQueue extends MQPublishPoint, MQSubscribePoint {
|
||||
|
||||
}
|
48
poison-pill/src/main/java/com/iluwatar/Producer.java
Normal file
48
poison-pill/src/main/java/com/iluwatar/Producer.java
Normal file
@ -0,0 +1,48 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.iluwatar.Message.Headers;
|
||||
|
||||
/**
|
||||
* Class responsible for producing unit of work that can be expressed as message and submitted to queue
|
||||
*/
|
||||
public class Producer {
|
||||
|
||||
private final MQPublishPoint queue;
|
||||
private final String name;
|
||||
private boolean isStopped;
|
||||
|
||||
public Producer(String name, MQPublishPoint queue) {
|
||||
this.name = name;
|
||||
this.queue = queue;
|
||||
this.isStopped = false;
|
||||
}
|
||||
|
||||
public void send(String body) {
|
||||
if (isStopped) {
|
||||
throw new IllegalStateException(String.format("Producer %s was stopped and fail to deliver requested message [%s].", body, name));
|
||||
}
|
||||
Message msg = new SimpleMessage();
|
||||
msg.addHeader(Headers.DATE, new Date().toString());
|
||||
msg.addHeader(Headers.SENDER, name);
|
||||
msg.setBody(body);
|
||||
|
||||
try {
|
||||
queue.put(msg);
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
isStopped = true;
|
||||
try {
|
||||
queue.put(Message.POISON_PILL);
|
||||
} catch (InterruptedException e) {
|
||||
// allow thread to exit
|
||||
System.err.println(e);
|
||||
}
|
||||
}
|
||||
}
|
39
poison-pill/src/main/java/com/iluwatar/SimpleMessage.java
Normal file
39
poison-pill/src/main/java/com/iluwatar/SimpleMessage.java
Normal file
@ -0,0 +1,39 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link Message} basic implementation
|
||||
*/
|
||||
public class SimpleMessage implements Message {
|
||||
|
||||
private Map<Headers, String> headers = new HashMap<>();
|
||||
private String body;
|
||||
|
||||
@Override
|
||||
public void addHeader(Headers header, String value) {
|
||||
headers.put(header, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(Headers header) {
|
||||
return headers.get(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Headers, String> getHeaders() {
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
* Bounded blocking queue wrapper
|
||||
*/
|
||||
public class SimpleMessageQueue implements MessageQueue {
|
||||
|
||||
private final BlockingQueue<Message> queue;
|
||||
|
||||
public SimpleMessageQueue(int bound) {
|
||||
queue = new ArrayBlockingQueue<Message>(bound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Message msg) throws InterruptedException {
|
||||
queue.put(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message take() throws InterruptedException {
|
||||
return queue.take();
|
||||
}
|
||||
|
||||
}
|
12
poison-pill/src/test/java/com/iluwatar/AppTest.java
Normal file
12
poison-pill/src/test/java/com/iluwatar/AppTest.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.iluwatar;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
String[] args = {};
|
||||
App.main(args);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user