-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fence child containers after ConcurrentContainer stops #3377
Conversation
|
||
/** | ||
* Exception throw when container is started when it is not a member of | ||
* ConcurrentMessageListenerContainer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure in this conclusion.
The KafkaMessageListenerContainer
can be used by itself, without being a member of the ConcurrentMessageListenerContainer
.
I think we talked about failing to start a child container when its parent is stopped.
Not sure also if we definitely need a dedicated exception on the matter.
I feel like regular IllegalStateException
is fully enough.
Because indeed we are in the illegal state to attempt to start that child container when its parent is stopped.
What do I miss, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From design perspective I thought, It would be better if we can provide a specific exception for this scenario. But, IllegalStateException
would also works here. I will do that change here if you suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think new exception would be benefical for the app developers. It improves the error handling. IllegalStateException would work, but it is really generic. From framework perspective, a seperate exception could be better. Please give your comments. I will modify accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
KafkaMessageListenerContainer
can be used by itself, without being a member of theConcurrentMessageListenerContainer
.I think we talked about failing to start a child container when its parent is stopped.
KafkaMessageListenerContainer can be used individually. Given changes does not modify in this regard. I feel expected spring-kafka system should be as follows.
Assumption: CMain -- ConcurrentContainer. C0, C1 are the child containers. Choosen concurrency 2.
Flow:
-
CMain -- started.
This would inturn starts C0 and C1.CMain -- running.
C0 -- running
C1 -- running. -
C0 stopped. This should be allowed.
CMain -- not running.
C0 -- not running
C1 -- running. -
C0 started. This should be allowed. Since, CMain is not stopped explicitly.
CMain -- running.
C0 -- running
C1 -- running. -
C0 stopped. This should be allowed. Since, CMain is not stopped explicitly.
CMain -- not running.
C0 -- not running
C1 -- running. -
C1 stopped. This should be allowed. Since, CMain is not stopped explicitly.
CMain -- not running.
C0 -- not running
C1 -- not running. -
C0 started. This should be allowed. Since, CMain is not stopped explicitly.
CMain -- not running.
C0 -- running
C1 -- not running. -
C1 started. This should be allowed. Since, CMain is not stopped explicitly.
CMain -- running.
C0 -- running
C1 -- running. -
CMain -- stopped explicitly.
This would in turn stops C0 and C1. C0 and C1 would be cleared.CMain -- not running.
C0 -- not running. delinked
C1 -- not running. delinked. -
C0 started. This should not be allowed. Since, CMain is stopped explicitly. Container Should be Fenced.
-
CMain -- started.
This would inturn starts C2 and C3.
CMain -- running.
C2 -- running
C3 -- running.
C0 -- not running. delinked
C1 -- not running. delinked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The custom exception makes sense if it is intended to be used in the flow control.
In this case it is really abnormal to try to start the container which has not connection with its parent.
Therefore such a logic is wrong and has to be fixed in the target project.
So, IllegalStateException
would just do whatever we need to indicated is wrong.
I'm not sure in your use-cases explanation, but looking into behavior one more time, I feel like we have to modify ConcurrentMessageListenerContainer.doStop(final Runnable callback, boolean normal)
to call destroy()
on all those child containers before this.containers.clear();
.
That destroy()
should call not only stop();
as it is right now in the MessageListenerContainer
, but also mark some flag like destroyed
.
Then when we try to call start()
on that child container this new flag would give us the mentioned IllegalStateException
indicating that container cannot be restarted after being destroyed.
Does that make sense to you?
Thank you for being patient with my review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I have taken the core idea from your comments and modified the logic to solve this issue. I have planned to use containerAllowedToStart
flag to verify if the container is allowed to start. Please consider.
I have not used here destroy
API for the following reasons.
destroy
does not support to take the callback.- A race could occur to start the container immediately after stopped, if
destroy
is called before clearing the containers and after stopping the containers.
Containers are not restricted from starting after ConcurrentContainer stopped or restarted. These changes would fix this issue.
These changes would use containerAllowedToStart flag to verify if the container is allowed to start rather doing a member check. A new API is added to the MessageListenerContainer. This flag would be set prior to stopping the container to ensure race conditions would not occur start the container once container is stopped immediately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm OK with the rest of logic.
Thank you!
* @return true if a container is allowed to start. | ||
* @since 2.7.3 | ||
*/ | ||
default boolean isAllowedToStart() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need any new public API to complicate a a logic of the target project more.
The child container after stopping from the concurrent one is effectively orphan, therefore it is totally OK to indicate such an illegal state throwing an exception from the doStart()
of that container.
If you got that exception, then something is wrong with application logic and it has to be fixed.
Much more robust solution and decision, than trying to if..else
it via this new API.
More over that Spring Kafka version is out of support: https://spring.io/projects/spring-kafka#support.
So, even if we will decide to back-port the fix, it won't make it into that EOL version anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More over that Spring Kafka version is out of support: https://spring.io/projects/spring-kafka#support. So, even if we will decide to back-port the fix, it won't make it into that EOL version anyway.
My mistake. I have not update the version to 3.3. As per your comments, I have removed the API.
@@ -355,6 +355,7 @@ protected void doStop(final Runnable callback, boolean normal) { | |||
} | |||
} | |||
for (KafkaMessageListenerContainer<K, V> container : this.containers) { | |||
container.setAllowedToStart(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would really be more specific here and call it setOrphaned()
(or fenced
) as you call it in the exception message.
This commit includes following changes. 1. Remove API isAllowedToStart added in previous commit from MessageListenerContainer 2. Rename setAllowedStart to setFenced.
@@ -732,4 +742,10 @@ protected Properties propertiesFromConsumerPropertyOverrides() { | |||
return props; | |||
} | |||
|
|||
public boolean isContainerFenced() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again: I don't see a reason in any new API like this.
The functionality is totally connected with the start()
and if you fail there, then something is off in your logic.
Don't give the framework users to make some choice which would be possible with this new public API.
Just throw that IllegalStateException
from the start.
If you meet such an exception, then something has to be fixed in the target project.
this.fenced = fenced; | ||
} | ||
|
||
public boolean isFenced() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I even don't see a reason in this one.
That is fully internal logic of the container architecture.
And if you have to deal with this new public API, then the logic in your project is off.
The consumer of the framework must use it in a proper way.
So, any child containers must be controlled outside of the parent container, when that parent container has abandoned them.
Would you sharing with us some idea what is the use-case that we would need to deal with child lifecycle individually, but not via its parent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not suggesting child container lifecycle has to be managed out side of the parent container lifecycle. It would be highly beneficial in some scenrio's to start and stop the containers individually provided parent container is live. In fact, child container should be no more useful when the parent container has abandoned.
Your comment is correct. APIs are internal and need not be public.
Use case: Assume a highly concurrent spring-kafka application. Let one of the container is stopped for some reason. It would be a nice feature to start the container alone rather than stopping all the containers and starting again.
...kafka/src/main/java/org/springframework/kafka/listener/AbstractMessageListenerContainer.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Will be merged when PR build is green.
Thank you for outstanding contribution!
Thanks a lot!! |
Containers are not restricted from starting after ConcurrentContainer stopped or restarted. These changes would fix this issue.