mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 08:58:26 +00:00
docs: update poison pill
This commit is contained in:
+61
-37
@@ -1,24 +1,27 @@
|
||||
---
|
||||
title: Poison Pill
|
||||
category: Behavioral
|
||||
category: Concurrency
|
||||
language: en
|
||||
tag:
|
||||
- Cloud distributed
|
||||
- Reactive
|
||||
- Decoupling
|
||||
- Fault tolerance
|
||||
- Messaging
|
||||
- Thread management
|
||||
---
|
||||
|
||||
## Also known as
|
||||
|
||||
* Shutdown Signal
|
||||
|
||||
## Intent
|
||||
|
||||
Poison Pill is known predefined data item that allows to provide graceful shutdown for separate
|
||||
distributed consumption process.
|
||||
The Poison Pill design pattern is used to gracefully shut down a service or a producer-consumer system by sending a special message (the "poison pill") which indicates that no more messages will be sent, allowing the consumers to terminate.
|
||||
|
||||
## Explanation
|
||||
|
||||
Real world example
|
||||
|
||||
> Let's think about a message queue with one producer and one consumer. The producer keeps pushing
|
||||
> new messages in the queue and the consumer keeps reading them. Finally when it's time to
|
||||
> gracefully shut down the producer sends the poison pill message.
|
||||
> A real-world analogy for the Poison Pill design pattern is the use of a "closed" sign in a retail store. When the store is ready to close for the day, the manager places a "closed" sign on the door. This sign acts as a signal to any new customers that no more customers will be admitted, but it doesn't immediately force out the customers already inside. The store staff will then attend to the remaining customers, allowing them to complete their purchases before finally locking up and turning off the lights. Similarly, in the Poison Pill pattern, a special "poison pill" message signals consumers to stop accepting new tasks while allowing them to finish processing the current tasks before shutting down gracefully.
|
||||
|
||||
In plain words
|
||||
|
||||
@@ -26,13 +29,12 @@ In plain words
|
||||
|
||||
**Programmatic Example**
|
||||
|
||||
Let's define the message structure first. There's interface `Message` and implementation
|
||||
`SimpleMessage`.
|
||||
Let's define the message structure first. There's interface `Message` and implementation `SimpleMessage`.
|
||||
|
||||
```java
|
||||
public interface Message {
|
||||
|
||||
...
|
||||
// ...
|
||||
|
||||
enum Headers {
|
||||
DATE, SENDER
|
||||
@@ -81,23 +83,18 @@ public class SimpleMessage implements Message {
|
||||
}
|
||||
```
|
||||
|
||||
To pass messages we are using message queues. Here we define the types related to the message queue:
|
||||
`MqPublishPoint`, `MqSubscribePoint` and `MessageQueue`. `SimpleMessageQueue` implements all these
|
||||
interfaces.
|
||||
To pass messages we are using message queues. Here we define the types related to the message queue: `MqPublishPoint`, `MqSubscribePoint` and `MessageQueue`. `SimpleMessageQueue` implements all these interfaces.
|
||||
|
||||
```java
|
||||
public interface MqPublishPoint {
|
||||
|
||||
void put(Message msg) throws InterruptedException;
|
||||
}
|
||||
|
||||
public interface MqSubscribePoint {
|
||||
|
||||
Message take() throws InterruptedException;
|
||||
}
|
||||
|
||||
public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {
|
||||
}
|
||||
public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {}
|
||||
|
||||
public class SimpleMessageQueue implements MessageQueue {
|
||||
|
||||
@@ -119,14 +116,12 @@ public class SimpleMessageQueue implements MessageQueue {
|
||||
}
|
||||
```
|
||||
|
||||
Next we need message `Producer` and `Consumer`. Internally they use the message queues from above.
|
||||
It's important to notice that when `Producer` stops, it sends out the poison pill to inform
|
||||
`Consumer` that the messaging has finished.
|
||||
Next, we need message `Producer` and `Consumer`. Internally they use the message queues from above. It's important to notice that when `Producer` stops, it sends out the poison pill to inform `Consumer` that the messaging has finished.
|
||||
|
||||
```java
|
||||
public class Producer {
|
||||
|
||||
...
|
||||
// ...
|
||||
|
||||
public void send(String body) {
|
||||
if (isStopped) {
|
||||
@@ -159,7 +154,7 @@ public class Producer {
|
||||
|
||||
public class Consumer {
|
||||
|
||||
...
|
||||
// ...
|
||||
|
||||
public void consume() {
|
||||
while (true) {
|
||||
@@ -182,21 +177,21 @@ public class Consumer {
|
||||
}
|
||||
```
|
||||
|
||||
Finally we are ready to present the whole example in action.
|
||||
Finally, we are ready to present the whole example in action.
|
||||
|
||||
```java
|
||||
var queue = new SimpleMessageQueue(10000);
|
||||
var queue = new SimpleMessageQueue(10000);
|
||||
|
||||
final var producer = new Producer("PRODUCER_1", queue);
|
||||
final var consumer = new Consumer("CONSUMER_1", queue);
|
||||
final var producer = new Producer("PRODUCER_1", queue);
|
||||
final var consumer = new Consumer("CONSUMER_1", queue);
|
||||
|
||||
new Thread(consumer::consume).start();
|
||||
new Thread(consumer::consume).start();
|
||||
|
||||
new Thread(() -> {
|
||||
producer.send("hand shake");
|
||||
producer.send("some very important information");
|
||||
producer.send("bye!");
|
||||
producer.stop();
|
||||
new Thread(() -> {
|
||||
producer.send("hand shake");
|
||||
producer.send("some very important information");
|
||||
producer.send("bye!");
|
||||
producer.stop();
|
||||
}).start();
|
||||
```
|
||||
|
||||
@@ -211,14 +206,43 @@ Consumer CONSUMER_1 receive request to terminate.
|
||||
|
||||
## Class diagram
|
||||
|
||||

|
||||

|
||||
|
||||
## Applicability
|
||||
|
||||
Use the Poison Pill idiom when:
|
||||
|
||||
* There's a need to send signal from one thread/process to another to terminate.
|
||||
* When there is a need to gracefully shut down a multithreaded application.
|
||||
* In producer-consumer scenarios where consumers need to be informed about the end of message processing.
|
||||
* To ensure that consumers can finish processing remaining messages before shutting down.
|
||||
|
||||
## Real world examples
|
||||
## Known Uses
|
||||
|
||||
* [akka.actor.PoisonPill](http://doc.akka.io/docs/akka/2.1.4/java/untyped-actors.html)
|
||||
* Java ExecutorService shutdown using a special task to signal shutdown.
|
||||
* Messaging systems where a specific message indicates the end of the queue processing.
|
||||
* [Akka framework](https://doc.akka.io/japi/akka/2.5/akka/actor/typed/internal/PoisonPill.html)
|
||||
|
||||
## Consequences
|
||||
|
||||
Benefits:
|
||||
|
||||
* Simplifies the shutdown process of consumers.
|
||||
* Ensures that all pending tasks are completed before termination.
|
||||
* Decouples the shutdown logic from the main processing logic.
|
||||
|
||||
Trade-offs:
|
||||
|
||||
* Requires consumers to check for the poison pill, adding some overhead.
|
||||
* If not managed properly, could lead to consumers not recognizing the poison pill, causing indefinite blocking.
|
||||
|
||||
## Related Patterns
|
||||
|
||||
* [Producer-Consumer](https://java-design-patterns.com/patterns/producer-consumer/): Works in tandem with the Poison Pill pattern to handle the communication and shutdown of consumers.
|
||||
* Message Queue: Often uses poison pills to signal the end of message processing in the queue.
|
||||
* [Observer](https://java-design-patterns.com/patterns/observer/): Can be used to notify subscribers about the shutdown event.
|
||||
|
||||
## Credits
|
||||
|
||||
* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
|
||||
* [Java Concurrency in Practice](https://amzn.to/4aRMruW)
|
||||
* [Effective Java](https://amzn.to/4cGk2Jz)
|
||||
|
||||
@@ -92,7 +92,7 @@ class ConsumerTest {
|
||||
return msg;
|
||||
}
|
||||
|
||||
private class InMemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
private static class InMemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
private final List<ILoggingEvent> log = new LinkedList<>();
|
||||
|
||||
public InMemoryAppender(Class clazz) {
|
||||
|
||||
@@ -39,16 +39,12 @@ class PoisonMessageTest {
|
||||
|
||||
@Test
|
||||
void testAddHeader() {
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
POISON_PILL.addHeader(Headers.SENDER, "sender");
|
||||
});
|
||||
assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.addHeader(Headers.SENDER, "sender"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetHeader() {
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
POISON_PILL.getHeader(Headers.SENDER);
|
||||
});
|
||||
assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.getHeader(Headers.SENDER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -58,9 +54,7 @@ class PoisonMessageTest {
|
||||
|
||||
@Test
|
||||
void testSetBody() {
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
POISON_PILL.setBody("Test message.");
|
||||
});
|
||||
assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.setBody("Test message."));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -56,10 +56,6 @@ class SimpleMessageTest {
|
||||
void testUnModifiableHeaders() {
|
||||
final var message = new SimpleMessage();
|
||||
final var headers = message.getHeaders();
|
||||
assertThrows(UnsupportedOperationException.class, () -> {
|
||||
headers.put(Message.Headers.SENDER, "test");
|
||||
});
|
||||
assertThrows(UnsupportedOperationException.class, () -> headers.put(Message.Headers.SENDER, "test"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user