diff --git a/README.md b/README.md
index f584edc61..90d487b91 100644
--- a/README.md
+++ b/README.md
@@ -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)
## Abstract Factory [↑](#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
+## Poison Pill [↑](#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
diff --git a/poison-pill/etc/poison-pill.png b/poison-pill/etc/poison-pill.png
new file mode 100644
index 000000000..bfca5848e
Binary files /dev/null and b/poison-pill/etc/poison-pill.png differ
diff --git a/poison-pill/etc/poison-pill.ucls b/poison-pill/etc/poison-pill.ucls
new file mode 100644
index 000000000..6d20b1974
--- /dev/null
+++ b/poison-pill/etc/poison-pill.ucls
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml
new file mode 100644
index 000000000..1d9d4c38a
--- /dev/null
+++ b/poison-pill/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.0-SNAPSHOT
+
+ poison-pill
+
+
+ junit
+ junit
+ test
+
+
+
diff --git a/poison-pill/src/main/java/com/iluwatar/App.java b/poison-pill/src/main/java/com/iluwatar/App.java
new file mode 100644
index 000000000..22de1dd28
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/App.java
@@ -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();
+ }
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/Consumer.java b/poison-pill/src/main/java/com/iluwatar/Consumer.java
new file mode 100644
index 000000000..499971b6c
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/Consumer.java
@@ -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));
+ }
+ }
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/MQPublishPoint.java b/poison-pill/src/main/java/com/iluwatar/MQPublishPoint.java
new file mode 100644
index 000000000..6cc830e71
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/MQPublishPoint.java
@@ -0,0 +1,9 @@
+package com.iluwatar;
+
+/**
+ * Endpoint to publish {@link Message} to queue
+ */
+public interface MQPublishPoint {
+
+ public void put(Message msg) throws InterruptedException;
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/MQSubscribePoint.java b/poison-pill/src/main/java/com/iluwatar/MQSubscribePoint.java
new file mode 100644
index 000000000..5e15ba7b9
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/MQSubscribePoint.java
@@ -0,0 +1,9 @@
+package com.iluwatar;
+
+/**
+ * Endpoint to retrieve {@link Message} from queue
+ */
+public interface MQSubscribePoint {
+
+ public Message take() throws InterruptedException;
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/Message.java b/poison-pill/src/main/java/com/iluwatar/Message.java
new file mode 100644
index 000000000..e9a67fe59
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/Message.java
@@ -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 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 getHeaders();
+ public void setBody(String body);
+ public String getBody();
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/MessageQueue.java b/poison-pill/src/main/java/com/iluwatar/MessageQueue.java
new file mode 100644
index 000000000..03ced489b
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/MessageQueue.java
@@ -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 {
+
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/Producer.java b/poison-pill/src/main/java/com/iluwatar/Producer.java
new file mode 100644
index 000000000..89fb75277
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/Producer.java
@@ -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);
+ }
+ }
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/SimpleMessage.java b/poison-pill/src/main/java/com/iluwatar/SimpleMessage.java
new file mode 100644
index 000000000..27b8db08b
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/SimpleMessage.java
@@ -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 = 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 getHeaders() {
+ return Collections.unmodifiableMap(headers);
+ }
+
+ @Override
+ public void setBody(String body) {
+ this.body = body;
+ }
+
+ @Override
+ public String getBody() {
+ return body;
+ }
+}
diff --git a/poison-pill/src/main/java/com/iluwatar/SimpleMessageQueue.java b/poison-pill/src/main/java/com/iluwatar/SimpleMessageQueue.java
new file mode 100644
index 000000000..12d519ce2
--- /dev/null
+++ b/poison-pill/src/main/java/com/iluwatar/SimpleMessageQueue.java
@@ -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 queue;
+
+ public SimpleMessageQueue(int bound) {
+ queue = new ArrayBlockingQueue(bound);
+ }
+
+ @Override
+ public void put(Message msg) throws InterruptedException {
+ queue.put(msg);
+ }
+
+ @Override
+ public Message take() throws InterruptedException {
+ return queue.take();
+ }
+
+}
diff --git a/poison-pill/src/test/java/com/iluwatar/AppTest.java b/poison-pill/src/test/java/com/iluwatar/AppTest.java
new file mode 100644
index 000000000..6db5ad214
--- /dev/null
+++ b/poison-pill/src/test/java/com/iluwatar/AppTest.java
@@ -0,0 +1,12 @@
+package com.iluwatar;
+
+import org.junit.Test;
+
+public class AppTest {
+
+ @Test
+ public void test() {
+ String[] args = {};
+ App.main(args);
+ }
+}
diff --git a/pom.xml b/pom.xml
index d81be42c9..d05182607 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,6 +44,7 @@
execute-around
property
intercepting-filter
+ poison-pill