Class FcSampleSseServlet
java.lang.Object
jakarta.servlet.GenericServlet
jakarta.servlet.http.HttpServlet
org.frontcache.resilience.stream.FcSampleSseServlet
- All Implemented Interfaces:
jakarta.servlet.Servlet, jakarta.servlet.ServletConfig, Serializable
- Direct Known Subclasses:
FcDashboardStreamServlet
public abstract class FcSampleSseServlet
extends jakarta.servlet.http.HttpServlet
Serves the SSE dashboard stream at
/fc-dashboard.stream and at the pre-2.7
/hystrix.stream, from Frontcache's own metrics instead of Hystrix's.
The SSE framing, the 500 ms cadence and the maxConcurrentConnections throttle are exactly
what they were. What changed, in two steps:
PR 3 removed RxJava
Observable.concatMap(...).observeOn(Schedulers.io()) became one scheduled publisher plus one
queue per subscriber. Frames are built ONCE per tick rather than once per subscriber, so fifteen
dashboards cost one serialization rather than fifteen.
2.7 removed the parked thread
Until now each connection held a container thread for its entire life - originally asleep, then blocked on its own queue. At the default cap that is five threads, at a tuned fifteen it is fifteen, doing nothing but waiting 500 ms at a time. On a node whose thread pool also serves traffic, leaving a dashboard tab open all afternoon costs a request-serving thread all afternoon. This is now Servlet 3.1 async with non-blocking output:doGet validates, calls
startAsync(), registers a WriteListener and returns, handing the container
thread straight back. No thread is dedicated to a viewer at all - writes happen either on the
publisher thread (when the connection is ready to take a frame) or on a container thread the
container hands us via onWritePossible() (when it was not, and now is).
Why the writes are non-blocking rather than "publisher writes and blocks"
Because that is the whole difficulty of the change. A single publisher thread doing blocking writes would be simple and would reintroduce precisely what PR 3 fixed: one reader behind TCP backpressure stalls every other dashboard.isReady() is what avoids it - a write is attempted only when
the container promises it will not block, so a stalled reader just stops being written to. Its own
queue fills, its oldest frames are dropped, and no other connection notices.
The idle handoff
onWritePossible() is only called on a not-ready -> ready transition, so a subscriber that
drains its queue while still ready will never be called again on its own. That is why
FcSampleSseServlet.Subscriber.offer(byte[]) also drains: whichever of the two threads finds work to do does it, under
one lock per subscriber so that only one of them is inside isReady()/write() at a
time. The lock is held only across non-blocking writes, so a container thread can never be parked
on it for longer than it takes to fill a socket buffer.
The connection count
The cap is now released fromFcSampleSseServlet.Subscriber.close(), not from a finally around the request,
because the request ends immediately and the connection does not. close() is
compare-and-set guarded so the decrement happens exactly once however the connection ends - client
disconnect, write error, container timeout, or shutdown. Getting this wrong would not fail visibly
at once: the endpoint would simply start answering 503 forever after N lifetime connections, which
is why StandaloneDashboardStreamTests asserts a closed connection frees its slot.
Two behavior changes, both from proposal section 14.6
- A wrong or absent API key gets401, where it used to get 200
and an endless stream of empty frames. The header is this endpoint's only authentication, so an
unauthenticated read should be refused rather than answered with an empty success.
- 503 now means only what it says: the service is shutting down, or the connection cap is
reached. The old code answered every case, including "your key is wrong", the same way.- See Also:
-
Field Summary
FieldsModifier and TypeFieldDescriptionprotected static final intPublish cadence in ms.Fields inherited from class jakarta.servlet.http.HttpServlet
LEGACY_DO_HEAD -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected abstract voidvoiddestroy()protected voiddoGet(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) protected abstract intprotected abstract intprotected abstract intvoidinit()static voidshutdown()WebSphere won't shutdown a servlet until after a 60 second timeout if there is an instance of the servlet executing a request.Methods inherited from class jakarta.servlet.http.HttpServlet
doDelete, doHead, doOptions, doPost, doPut, doTrace, getLastModified, init, service, serviceMethods inherited from class jakarta.servlet.GenericServlet
getInitParameter, getInitParameterNames, getServletConfig, getServletContext, getServletInfo, getServletName, log, log
-
Field Details
-
DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS
protected static final int DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MSPublish cadence in ms. Named for the field it replaced, which was a poll timeout.- See Also:
-
-
Constructor Details
-
FcSampleSseServlet
protected FcSampleSseServlet(int pausePollerThreadDelayInMs)
-
-
Method Details
-
getMaxNumberConcurrentConnectionsAllowed
protected abstract int getMaxNumberConcurrentConnectionsAllowed() -
getNumberCurrentConnections
protected abstract int getNumberCurrentConnections() -
incrementAndGetCurrentConcurrentConnections
protected abstract int incrementAndGetCurrentConcurrentConnections() -
decrementCurrentConcurrentConnections
protected abstract void decrementCurrentConcurrentConnections() -
doGet
protected void doGet(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws jakarta.servlet.ServletException, IOException - Overrides:
doGetin classjakarta.servlet.http.HttpServlet- Throws:
jakarta.servlet.ServletExceptionIOException
-
shutdown
public static void shutdown()WebSphere won't shutdown a servlet until after a 60 second timeout if there is an instance of the servlet executing a request. Add this method to enable a hook to notify Frontcache to shutdown. You must invoke this method at shutdown, perhaps from some other servlet's destroy() method. -
init
public void init() throws jakarta.servlet.ServletException- Overrides:
initin classjakarta.servlet.GenericServlet- Throws:
jakarta.servlet.ServletException
-
destroy
public void destroy()- Specified by:
destroyin interfacejakarta.servlet.Servlet- Overrides:
destroyin classjakarta.servlet.GenericServlet
-