Skip to content
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

Merged
merged 10 commits into from
Jul 31, 2024

Conversation

LokeshAlamuri
Copy link
Contributor

Containers are not restricted from starting after ConcurrentContainer stopped or restarted. These changes would fix this issue.


/**
* Exception throw when container is started when it is not a member of
* ConcurrentMessageListenerContainer.
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

@LokeshAlamuri LokeshAlamuri Jul 20, 2024

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 the ConcurrentMessageListenerContainer.

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:

  1. CMain -- started.
    This would inturn starts C0 and C1.

    CMain -- running.
    C0 -- running
    C1 -- running.

  2. C0 stopped. This should be allowed.

    CMain -- not running.
    C0 -- not running
    C1 -- running.

  3. C0 started. This should be allowed. Since, CMain is not stopped explicitly.

    CMain -- running.
    C0 -- running
    C1 -- running.

  4. C0 stopped. This should be allowed. Since, CMain is not stopped explicitly.

    CMain -- not running.
    C0 -- not running
    C1 -- running.

  5. C1 stopped. This should be allowed. Since, CMain is not stopped explicitly.

    CMain -- not running.
    C0 -- not running
    C1 -- not running.

  6. C0 started. This should be allowed. Since, CMain is not stopped explicitly.

    CMain -- not running.
    C0 -- running
    C1 -- not running.

  7. C1 started. This should be allowed. Since, CMain is not stopped explicitly.

    CMain -- running.
    C0 -- running
    C1 -- running.

  8. 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.

  9. C0 started. This should not be allowed. Since, CMain is stopped explicitly. Container Should be Fenced.

  10. CMain -- started.
    This would inturn starts C2 and C3.

CMain -- running.
C2 -- running
C3 -- running.

C0 -- not running. delinked
C1 -- not running. delinked.

Copy link
Member

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!

Copy link
Contributor Author

@LokeshAlamuri LokeshAlamuri Jul 25, 2024

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.

  1. destroy does not support to take the callback.
  2. A race could occur to start the container immediately after stopped, if destroy is called before clearing the containers and after stopping the containers.

Lokesh Alamuri added 3 commits July 25, 2024 12:56
Containers are not restricted from starting after ConcurrentContainer stopped or restarted. These changes would fix this issue.
Lokesh Alamuri added 4 commits July 25, 2024 16:13
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.
Copy link
Member

@artembilan artembilan left a 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() {
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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() {
Copy link
Member

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() {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

@artembilan artembilan left a 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!

@LokeshAlamuri
Copy link
Contributor Author

LGTM. Will be merged when PR build is green. Thank you for outstanding contribution!

Thanks a lot!!

@artembilan artembilan enabled auto-merge (squash) July 31, 2024 16:54
@artembilan artembilan merged commit 20696f2 into spring-projects:main Jul 31, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants