Skip to content

Commit dc0f578

Browse files
committed
added Poison Pill idiom
1 parent 6366041 commit dc0f578

15 files changed

+398
-1
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ A programming idiom is a means of expressing a recurring construct in one or mor
7171

7272
* [Execute Around](#execute-around)
7373
* [Double Checked Locking](#double-checked-locking)
74-
74+
* [Poison Pill](#poison-pill)
7575

7676

7777
## <a name="abstract-factory">Abstract Factory</a> [&#8593;](#list-of-design-patterns)
@@ -475,6 +475,13 @@ A programming idiom is a means of expressing a recurring construct in one or mor
475475
**Real world examples:**
476476
* [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain) prototype inheritance
477477

478+
## <a name="poison-pill">Poison Pill</a> [&#8593;](#list-of-design-patterns)
479+
**Intent:** Poison Pill is known predefined data item that allows to provide graceful shutdown for separate distributed consumption process.
480+
481+
![alt text](https://github.com/iluwatar/java-design-patterns/blob/master/poison-pill/etc/poison-pill.png "Poison Pill")
482+
483+
**Applicability:** Use the Poison Pill idiom when
484+
* need to send signal from one thread/process to another to terminate
478485

479486
# Frequently asked questions
480487

poison-pill/etc/poison-pill.png

15.5 KB
Loading

poison-pill/etc/poison-pill.ucls

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
3+
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
4+
<class id="1" language="java" name="com.iluwatar.Producer" project="poison-pill"
5+
file="/poison-pill/src/main/java/com/iluwatar/Producer.java" binary="false" corner="BOTTOM_RIGHT">
6+
<position height="153" width="182" x="158" y="209"/>
7+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
8+
sort-features="false" accessors="true" visibility="true">
9+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
10+
<operations public="true" package="true" protected="true" private="true" static="true"/>
11+
</display>
12+
</class>
13+
<interface id="2" language="java" name="com.iluwatar.MQPublishPoint" project="poison-pill"
14+
file="/poison-pill/src/main/java/com/iluwatar/MQPublishPoint.java" binary="false" corner="BOTTOM_RIGHT">
15+
<position height="-1" width="-1" x="453" y="283"/>
16+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
17+
sort-features="false" accessors="true" visibility="true">
18+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
19+
<operations public="true" package="true" protected="true" private="true" static="true"/>
20+
</display>
21+
</interface>
22+
<class id="3" language="java" name="com.iluwatar.SimpleMessageQueue" project="poison-pill"
23+
file="/poison-pill/src/main/java/com/iluwatar/SimpleMessageQueue.java" binary="false" corner="BOTTOM_RIGHT">
24+
<position height="-1" width="-1" x="627" y="115"/>
25+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
26+
sort-features="false" accessors="true" visibility="true">
27+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
28+
<operations public="true" package="true" protected="true" private="true" static="true"/>
29+
</display>
30+
</class>
31+
<interface id="4" language="java" name="com.iluwatar.MessageQueue" project="poison-pill"
32+
file="/poison-pill/src/main/java/com/iluwatar/MessageQueue.java" binary="false" corner="BOTTOM_RIGHT">
33+
<position height="-1" width="-1" x="627" y="283"/>
34+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
35+
sort-features="false" accessors="true" visibility="true">
36+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
37+
<operations public="true" package="true" protected="true" private="true" static="true"/>
38+
</display>
39+
</interface>
40+
<interface id="5" language="java" name="com.iluwatar.MQSubscribePoint" project="poison-pill"
41+
file="/poison-pill/src/main/java/com/iluwatar/MQSubscribePoint.java" binary="false" corner="BOTTOM_RIGHT">
42+
<position height="-1" width="-1" x="821" y="282"/>
43+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
44+
sort-features="false" accessors="true" visibility="true">
45+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
46+
<operations public="true" package="true" protected="true" private="true" static="true"/>
47+
</display>
48+
</interface>
49+
<class id="6" language="java" name="com.iluwatar.Consumer" project="poison-pill"
50+
file="/poison-pill/src/main/java/com/iluwatar/Consumer.java" binary="false" corner="BOTTOM_RIGHT">
51+
<position height="-1" width="-1" x="1055" y="282"/>
52+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
53+
sort-features="false" accessors="true" visibility="true">
54+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
55+
<operations public="true" package="true" protected="true" private="true" static="true"/>
56+
</display>
57+
</class>
58+
<generalization id="7">
59+
<end type="SOURCE" refId="4"/>
60+
<end type="TARGET" refId="2"/>
61+
</generalization>
62+
<realization id="8">
63+
<end type="SOURCE" refId="3"/>
64+
<end type="TARGET" refId="4"/>
65+
</realization>
66+
<generalization id="9">
67+
<end type="SOURCE" refId="4"/>
68+
<end type="TARGET" refId="5"/>
69+
</generalization>
70+
<association id="10">
71+
<end type="SOURCE" refId="6" navigable="false">
72+
<attribute id="11" name="queue"/>
73+
<multiplicity id="12" minimum="0" maximum="1"/>
74+
</end>
75+
<end type="TARGET" refId="5" navigable="true"/>
76+
<display labels="true" multiplicity="true"/>
77+
</association>
78+
<association id="13">
79+
<end type="SOURCE" refId="1" navigable="false">
80+
<attribute id="14" name="queue"/>
81+
<multiplicity id="15" minimum="0" maximum="1"/>
82+
</end>
83+
<end type="TARGET" refId="2" navigable="true"/>
84+
<display labels="true" multiplicity="true"/>
85+
</association>
86+
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
87+
sort-features="false" accessors="true" visibility="true">
88+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
89+
<operations public="true" package="true" protected="true" private="true" static="true"/>
90+
</classifier-display>
91+
<association-display labels="true" multiplicity="true"/>
92+
</class-diagram>

poison-pill/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<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"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>com.iluwatar</groupId>
7+
<artifactId>java-design-patterns</artifactId>
8+
<version>1.0-SNAPSHOT</version>
9+
</parent>
10+
<artifactId>poison-pill</artifactId>
11+
<dependencies>
12+
<dependency>
13+
<groupId>junit</groupId>
14+
<artifactId>junit</artifactId>
15+
<scope>test</scope>
16+
</dependency>
17+
</dependencies>
18+
</project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.iluwatar;
2+
3+
/**
4+
* One of possible approaches to terminate Producer-Consumer pattern is using PoisonPill idiom.
5+
* If you use PoisonPill as termination signal then Producer is responsible to notify Consumer that exchange is over
6+
* and reject any further messages. Consumer receiving PoisonPill will stop to read messages from queue.
7+
* You also must ensure that PoisonPill will be last message that will be read from queue (if you have
8+
* prioritized queue than this can be tricky).
9+
* In simple cases as PoisonPill can be used just null-reference, but holding unique separate shared
10+
* object-marker (with name "Poison" or "PoisonPill") is more clear and self describing.
11+
*/
12+
public class App {
13+
14+
public static void main(String[] args) {
15+
MessageQueue queue = new SimpleMessageQueue(10000);
16+
17+
final Producer producer = new Producer("PRODUCER_1", queue);
18+
final Consumer consumer = new Consumer("CONSUMER_1", queue);
19+
20+
new Thread() {
21+
@Override
22+
public void run() {
23+
consumer.consume();
24+
}
25+
}.start();
26+
27+
new Thread() {
28+
@Override
29+
public void run() {
30+
producer.send("hand shake");
31+
producer.send("some very important information");
32+
producer.send("bye!");
33+
producer.stop();
34+
}
35+
}.start();
36+
}
37+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.iluwatar;
2+
3+
import com.iluwatar.Message.Headers;
4+
5+
/**
6+
* Class responsible for receiving and handling submitted to the queue messages
7+
*/
8+
public class Consumer {
9+
10+
private final MQSubscribePoint queue;
11+
private final String name;
12+
13+
public Consumer(String name, MQSubscribePoint queue) {
14+
this.name = name;
15+
this.queue = queue;
16+
}
17+
18+
public void consume() {
19+
while (true) {
20+
Message msg;
21+
try {
22+
msg = queue.take();
23+
if (msg == Message.POISON_PILL) {
24+
System.out.println(String.format("Consumer %s receive request to terminate.", name));
25+
break;
26+
}
27+
} catch (InterruptedException e) {
28+
// allow thread to exit
29+
System.err.println(e);
30+
return;
31+
}
32+
33+
String sender = msg.getHeader(Headers.SENDER);
34+
String body = msg.getBody();
35+
System.out.println(String.format("Message [%s] from [%s] received by [%s]", body, sender, name));
36+
}
37+
}
38+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.iluwatar;
2+
3+
/**
4+
* Endpoint to publish {@link Message} to queue
5+
*/
6+
public interface MQPublishPoint {
7+
8+
public void put(Message msg) throws InterruptedException;
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.iluwatar;
2+
3+
/**
4+
* Endpoint to retrieve {@link Message} from queue
5+
*/
6+
public interface MQSubscribePoint {
7+
8+
public Message take() throws InterruptedException;
9+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.iluwatar;
2+
3+
import java.util.Map;
4+
5+
/**
6+
* Interface that implements the Message pattern and represents an inbound or outbound message as part of an {@link Producer}-{@link Consumer} exchange.
7+
*/
8+
public interface Message {
9+
10+
public static final Message POISON_PILL = new Message() {
11+
12+
@Override
13+
public void addHeader(Headers header, String value) {
14+
throw poison();
15+
}
16+
17+
@Override
18+
public String getHeader(Headers header) {
19+
throw poison();
20+
}
21+
22+
@Override
23+
public Map<Headers, String> getHeaders() {
24+
throw poison();
25+
}
26+
27+
@Override
28+
public void setBody(String body) {
29+
throw poison();
30+
}
31+
32+
@Override
33+
public String getBody() {
34+
throw poison();
35+
}
36+
37+
private RuntimeException poison() {
38+
return new UnsupportedOperationException("Poison");
39+
}
40+
41+
};
42+
43+
public enum Headers {
44+
DATE, SENDER
45+
}
46+
47+
public void addHeader(Headers header, String value);
48+
public String getHeader(Headers header);
49+
public Map<Headers, String> getHeaders();
50+
public void setBody(String body);
51+
public String getBody();
52+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.iluwatar;
2+
3+
/**
4+
* Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer}
5+
*/
6+
public interface MessageQueue extends MQPublishPoint, MQSubscribePoint {
7+
8+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.iluwatar;
2+
3+
import java.util.Date;
4+
5+
import com.iluwatar.Message.Headers;
6+
7+
/**
8+
* Class responsible for producing unit of work that can be expressed as message and submitted to queue
9+
*/
10+
public class Producer {
11+
12+
private final MQPublishPoint queue;
13+
private final String name;
14+
private boolean isStopped;
15+
16+
public Producer(String name, MQPublishPoint queue) {
17+
this.name = name;
18+
this.queue = queue;
19+
this.isStopped = false;
20+
}
21+
22+
public void send(String body) {
23+
if (isStopped) {
24+
throw new IllegalStateException(String.format("Producer %s was stopped and fail to deliver requested message [%s].", body, name));
25+
}
26+
Message msg = new SimpleMessage();
27+
msg.addHeader(Headers.DATE, new Date().toString());
28+
msg.addHeader(Headers.SENDER, name);
29+
msg.setBody(body);
30+
31+
try {
32+
queue.put(msg);
33+
} catch (InterruptedException e) {
34+
// allow thread to exit
35+
System.err.println(e);
36+
}
37+
}
38+
39+
public void stop() {
40+
isStopped = true;
41+
try {
42+
queue.put(Message.POISON_PILL);
43+
} catch (InterruptedException e) {
44+
// allow thread to exit
45+
System.err.println(e);
46+
}
47+
}
48+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.iluwatar;
2+
3+
import java.util.Collections;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
7+
/**
8+
* {@link Message} basic implementation
9+
*/
10+
public class SimpleMessage implements Message {
11+
12+
private Map<Headers, String> headers = new HashMap<>();
13+
private String body;
14+
15+
@Override
16+
public void addHeader(Headers header, String value) {
17+
headers.put(header, value);
18+
}
19+
20+
@Override
21+
public String getHeader(Headers header) {
22+
return headers.get(header);
23+
}
24+
25+
@Override
26+
public Map<Headers, String> getHeaders() {
27+
return Collections.unmodifiableMap(headers);
28+
}
29+
30+
@Override
31+
public void setBody(String body) {
32+
this.body = body;
33+
}
34+
35+
@Override
36+
public String getBody() {
37+
return body;
38+
}
39+
}

0 commit comments

Comments
 (0)