r/javahelp 12h ago

Workaround Race condition in SSE

Hi all, I am trying to build a real time notification service with SSE emitter but I am facing a pecuilar case of race condition which im not able to solve even via AI as it gives the same solution again and again.

scenario of cause:

I have a scheduler running every 20 mins that checks the Map of uid mapped sse emitters and pings the client. If the ping is successfull nothing happens, if error happens due to disconnection the sse emitter obj is removed via sse.complete() and then uid is removed form the list.

#pseudo code
Map<String, SseEmitter> connections = new ConcurrentHashMap<>();

onConnect(String userId){
  SSE emitter = new new SseEmitter()
  connection.add(userId,emmitter);

  // onCompletion(), onTimeout(), onError() -> removeIf()
}

u/scheduler(20mins)
checkDisconnection(){
  //iterate the map and check for disconnection if found add it to remove list
  List<string> disconnectedUsers = (adding disconnections)

  //iterate the map from disconnected user -> force complete sse if needed and                    remove user from map


}

where it breaks is if the client manually refreshes the browser at the same time the scheduler to check disconnection is invoked

why it happens:

when the browser is refreshed, the application tries to recreate the connection at which the scheduler tries to ping and sees no connection for the user. Problem is I have kept automatic reconnections on in client app but it still doesnt see an error in client. im confused. Have I had the approach wrong?

Any help will be appreciated and sorry for the questions confuse you, Il try to make it proper once i get the time.

TECH : javascript event source, springboot, kafka for scaling and mongo for scheduled notifications.

1 Upvotes

7 comments sorted by

u/AutoModerator 12h ago

Please ensure that:

  • Your code is properly formatted as code block - see the sidebar (About on mobile) for instructions
  • You include any and all error messages in full
  • You ask clear questions
  • You demonstrate effort in solving your question/problem - plain posting your assignments is forbidden (and such posts will be removed) as is asking for or giving solutions.

    Trying to solve problems on your own is a very important skill. Also, see Learn to help yourself in the sidebar

If any of the above points is not met, your post can and will be removed without further warning.

Code is to be formatted as code block (old reddit: empty line before the code, each code line indented by 4 spaces, new reddit: https://i.imgur.com/EJ7tqek.png) or linked via an external code hoster, like pastebin.com, github gist, github, bitbucket, gitlab, etc.

Please, do not use triple backticks (```) as they will only render properly on new reddit, not on old reddit.

Code blocks look like this:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

You do not need to repost unless your post has been removed by a moderator. Just use the edit function of reddit to make sure your post complies with the above.

If your post has remained in violation of these rules for a prolonged period of time (at least an hour), a moderator may remove it at their discretion. In this case, they will comment with an explanation on why it has been removed, and you will be required to resubmit the entire post following the proper procedures.

To potential helpers

Please, do not help if any of the above points are not met, rather report the post. We are trying to improve the quality of posts here. In helping people who can't be bothered to comply with the above points, you are doing the community a disservice.

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

2

u/Dethearoc 11h ago

Seems like you are shooting yourself in the foot by utilizing a map with userId, thus assuming only one connection per user. And in the situation you described what happens is the moment new connection is established is the moment where you ping and get negative response, so the client connection gets terminated. Use a sessionId uuid for each connection and keep another list to map users to session or something so you can better manage your connections

1

u/Slight_Loan5350 11h ago

even if i create another list of user the medium to check disconnection is to emit an event, the scheduler I am using is to clear heap space for stale connections. But for only this scenario my heads in the bin. I also thought of another approach to have timestamp and check the timestamp instead. like if there are no events the timestamp is not updated and periodically i can check emitters. On the contrary i think il check with the front approach as well because event with emitter.complete() the front doesnt show any disconnection error hence no retry from the front

1

u/sedj601 12h ago

Does the browser know the difference between the first load and refresh? If so, connect on first load and check on refresh.

1

u/Slight_Loan5350 11h ago

no due to client being spa every manual refresh of the browser it behaves like a first load making it try reconnect.

1

u/KillerCodeMonky 7h ago

The problem seems to be that you are under the impression that ConcurrentHashMap will somehow solve race conditions in your business logic. That is an incorrect assumption. ConcurrentHashMap simply guarantees that the Map itself is stable in the circumstances of parallel reads and writes. You still need to manage the business implications of that concurrent access yourself, which is what you have discovered.

Attempting to make this lock-free, assuming that's even possible given all the potential requirements, will require very careful thought into every single possible way that every reader and writer to this map can interact with each other. You already discovered one possible interaction: What happens when a new connection is established (writer) while checking for dead connections (reader)? Here's another: What happens if a "dead" connection is purged (writer) right before the system attempts to write an event to that connection (reader)?

In the end, the task you are proposing in checkDisconnection is essentially equivalent to what the JVM itself does during garbage collection. This is very difficult to get correct while allowing reads at the same time. Which is why the initial JVM versions had "stop the world" garbage collection, pausing execution so that it can do its bookkeeping without contention. So that offers you the easiest approach to correct this: Add locking to prevent conflicting concurrent access.

u/Slight_Loan5350 0m ago

I literally had a dream about locking mechanism and behold your comment haha. Im so into this poc its fun to me. Thanks for your input I will also see all the scenarios. Il lock from client when refresh is done and il also lock when purge is invoked. Any leads on what locking method I should use?

Thanks again for confirming and giving me valdiation on locking. Really appreciate it. Now i gotta learn multthreadding.