HABApp 25

Adding --bench did do a difference though. I now see this in OHs log:

WebSocket error: Blocking message pending 10000 for BLOCKING

The websocket is set up to time out after 10 seconds, so I guess what happens is that something the websocket is attempting to do is blocked/waiting for something else, and doesn’t complete within the 10 seconds, leading to the connection being closed. The big question is exactly what happens and why.

What does --bench do? Does it just try to establish a lot of connections, or does it try to retrieve something specific?

edit: I see now that it posts a lot of updates to some “testing Items”.

Looking at the code, I can’t quite see how the Core code can make anything block. But, there’s a difference from before, previously it would only process send one event at a time - and would block all sending OH threads in the meanwhile. Now it doesn’t wait, so events might be sent more rapidly. Is “your side” keeping up with the incoming events? If a message must wait more than 10 seconds before being consumed by the other side, the websocket will be disconnected, just like we see.

Since Python doesn’t really do threads, I don’t know how you’ve solved it, but could it be that something is clogging up the reception of the events?

edit (can’t make new posts until somebody else have replied):

I have gotten what seems to be somewhat interesting results. I have added a lot of debug logging on the OH side to see what happens when, and doing this slows everything down. That in itself has almost gotten rid of the disconnects, and it makes the benchmark report success:

+------------------------------------------------------------------------------+
|                                    HABApp                                    |
+------------------------------------------------------------------------------+

Bench events .. done!

               |  dur   | per sec | median |  min   |  max   |  mean
      rtt idle | 2.484s |  20.13k |  0.0us |  0.0us | 24.7ms | 49.7us
async rtt idle | 6.026s | 723.153 | 1.00ms | 0.55ms | 6.00ms | 1.38ms

+------------------------------------------------------------------------------+
|                                   openHAB                                    |
+------------------------------------------------------------------------------+

Bench item operations ... done!

            |  dur   | per sec | median |  min   |  max   |  mean
create item | 4.203s |  71.377 | 13.0ms | 9.00ms | 69.0ms | 14.0ms
update item | 6.078s |  49.360 | 19.0ms | 6.99ms | 0.370s | 20.3ms
delete item | 2.259s | 132.782 | 7.00ms | 5.97ms | 13.0ms | 7.53ms

Bench item state update ........ done!

                           |  dur   | per sec | median |  min   |  max   |  mean
                  rtt idle |  15.0s |  55.877 | 11.0ms | 5.96ms | 1.956s | 17.9ms
            async rtt idle |  14.8s |  56.243 | 10.0ms | 7.96ms | 2.649s | 17.8ms
             rtt idle item | 0.794s | 165.065 | 6.00ms | 3.96ms | 10.00ms | 6.06ms
       async rtt idle item | 4.018s |  90.587 | 4.00ms | 2.96ms | 2.308s | 11.0ms
           rtt load (+10x) | 7.925s |   7.950 | 67.0ms | 18.0ms | 2.049s | 0.126s
     async rtt load (+10x) |  15.9s |  73.275 | 8.00ms | 5.95ms | 2.319s | 13.6ms
      rtt load item (+10x) |  0.0us |   0.000 |  0.0us |  0.0us |  0.0us |  0.0us
async rtt load item (+10x) |  14.2s |  64.609 | 4.03ms | 2.95ms | 2.435s | 15.5ms

Cleanup ... complete

There are however still a few disconnects, but they don’t seem to “bother” the benchmark like it otherwise does. If I slowed down the code even more, my guess is that the disconnects could go away completely. This reinforces my suspicion that the problem is that the Python side doesn’t process the messages quickly enough, or not all of them in a timely fashion. So, this whole problem could be the result of OH now sending messages much quicker.

The OH side uses synchronous sends, so that send failures can be caught and logged. Jetty (the web server “engine” handling the low level websocket stuff) doesn’t support a “send queue” for synchronous sending of messages, so they must all be received within 10 seconds of trying to send them, or the connection is closed.

If I don’t care about logging errors, I could send them asynchronously. Jetty does implement a send queue for async sends as far as I understand, which would give “the other side” more time to get their ducks in a row without the connection being dropped. I’m not sure how best to handle error logging in that case though, but I guess it could be worth a try to make sending async to see if the problem goes away - and if that helps, try to figure out how to log errors.

Another possibility is to implement the queue in OH, and have a single thread that is responsible for processing that queue. That is a bit more work.

The question is how “much effort” OH should put into relieving remote endpoints from the burden of prompt processing of messages.

Any insight into how HABApp handles this would be useful, maybe there’s a simple “fix” that could be done there?

Wish I could help but my pythonian is not on this level, so hopefully @Spaceman_Spiff can answer when he’s got the time :smiling_face:

HABApp uses cooperative multitasking (sometimes called coroutines or asyncio).
It should be at least able to process >500msg/sec (see first benchmark where it tests the internal round trip time).

I’ve created a new bug where I added some information when the disconnect happens during my normal tests and not under high load. While still possible that there is an issue in HABApp, it rather points to openHAB since it happens when sending 5 messages in quick succession.
Could it be that this is some kind of race condition?
.

It could be, but if so, it seems to happen within Jetty, which is used very widely and should have been sufficiently battle-hardened. Jetty is very sparse with documenting exactly how they think this should be done, other than some old comments (2013) about “using multiple threads to write to a single remote is a bad idea” - but this seems to be from before they implemented their own locking system.

As an experiment, I’ve switched to using Jetty’s “async” send instead of “sync” send. The difference seems to be that the “async” send actually has a send queue, giving the remote endpoint more time to process messages (but the queue can go full, in which case the connection will also be dropped). The result is that I can run the benchmark without any disconnects.

It doesn’t seem to do anything substantial for speed, here is a benchmark with “sync send”:

(habapp) PS C:\Repos\Python\habapp> habapp --config C:\Repos\Python\habapp\conf --bench
  _   _    _    ____    _
 | | | |  / \  | __ )  / \   _ __  _ __
 | |_| | / _ \ |  _ \ / _ \ | '_ \| '_ \
 |  _  |/ ___ \| |_) / ___ \| |_) | |_) |
 |_| |_/_/   \_|____/_/   \_| .__/| .__/
                            |_|   |_|
                                        25.12.0
Entry "openhab.connection.buffer" was deleted
Entry "directories.param" moved to "directories.params"
"client_id" in mqtt.connection has been renamed to "identifier"
+------------------------------------------------------------------------------+
|                                    HABApp                                    |
+------------------------------------------------------------------------------+

Bench events .. done!

               |  dur   | per sec | median |  min   |  max   |  mean
      rtt idle | 3.115s |  16.05k |  0.0us |  0.0us | 26.0ms | 62.3us
async rtt idle | 6.019s | 547.638 | 2.00ms | 0.95ms | 37.0ms | 1.83ms

+------------------------------------------------------------------------------+
|                                   openHAB                                    |
+------------------------------------------------------------------------------+

Bench item operations ... done!

            |  dur   | per sec | median |  min   |  max   |  mean
create item | 5.035s |  59.588 | 16.0ms | 9.00ms | 33.0ms | 16.8ms
update item | 5.319s |  56.406 | 17.0ms | 7.04ms | 37.0ms | 17.7ms
delete item | 2.557s | 117.325 | 8.00ms | 6.96ms | 37.0ms | 8.52ms

Bench item state update ........ done!

                           |  dur   | per sec | median |  min   |  max   |  mean
                  rtt idle |  15.0s |  64.057 | 11.0ms | 6.96ms | 1.557s | 15.6ms
            async rtt idle |  14.4s |  70.666 | 11.0ms | 7.00ms | 1.631s | 14.2ms
             rtt idle item |  14.9s |  57.010 | 6.00ms | 3.97ms | 3.206s | 17.5ms
       async rtt idle item |  14.1s |  43.046 | 7.00ms | 4.00ms | 3.765s | 23.2ms
           rtt load (+10x) |  14.4s |   6.583 | 96.0ms | 23.0ms | 1.841s | 0.152s
     async rtt load (+10x) |  10.4s |   4.807 | 96.5ms | 24.0ms | 2.100s | 0.208s
      rtt load item (+10x) |  0.0us |   0.000 |  0.0us |  0.0us |  0.0us |  0.0us
async rtt load item (+10x) |  15.0s |  56.314 | 7.00ms | 4.00ms | 3.313s | 17.8ms

Cleanup ... complete

…and here is with “async send”:

(habapp) PS C:\Repos\Python\habapp> habapp --config C:\Repos\Python\habapp\conf --bench
  _   _    _    ____    _
 | | | |  / \  | __ )  / \   _ __  _ __
 | |_| | / _ \ |  _ \ / _ \ | '_ \| '_ \
 |  _  |/ ___ \| |_) / ___ \| |_) | |_) |
 |_| |_/_/   \_|____/_/   \_| .__/| .__/
                            |_|   |_|
                                        25.12.0
Entry "openhab.connection.buffer" was deleted
Entry "directories.param" moved to "directories.params"
"client_id" in mqtt.connection has been renamed to "identifier"
+------------------------------------------------------------------------------+
|                                    HABApp                                    |
+------------------------------------------------------------------------------+

Bench events .. done!

               |  dur   | per sec | median |  min   |  max   |  mean
      rtt idle | 3.338s |  14.98k |  0.0us |  0.0us | 26.0ms | 66.8us
async rtt idle | 6.026s | 593.777 | 2.00ms | 0.52ms | 6.00ms | 1.68ms

+------------------------------------------------------------------------------+
|                                   openHAB                                    |
+------------------------------------------------------------------------------+

Bench item operations ... done!

            |  dur   | per sec | median |  min   |  max   |  mean
create item | 4.674s |  64.191 | 15.0ms | 9.00ms | 29.0ms | 15.6ms
update item | 5.061s |  59.276 | 16.0ms | 8.00ms | 52.0ms | 16.9ms
delete item | 2.432s | 123.355 | 8.00ms | 6.00ms | 14.0ms | 8.11ms

Bench item state update ........ done!

                           |  dur   | per sec | median |  min   |  max   |  mean
                  rtt idle |  15.0s |  76.937 | 10.0ms | 7.00ms | 1.537s | 13.0ms
            async rtt idle |  15.0s |  65.935 | 11.0ms | 7.00ms | 1.829s | 15.2ms
             rtt idle item |  15.0s |  53.990 | 6.00ms | 3.96ms | 2.795s | 18.5ms
       async rtt idle item |  13.5s |  52.229 | 6.00ms | 4.00ms | 3.159s | 19.1ms
           rtt load (+10x) |  15.7s |   6.237 | 93.0ms | 27.0ms | 2.880s | 0.160s
     async rtt load (+10x) |  15.7s |   4.839 | 96.0ms | 26.0ms | 2.542s | 0.207s
      rtt load item (+10x) |  13.3s |   6.173 | 33.0ms | 22.0ms | 5.088s | 0.162s
async rtt load item (+10x) |  13.3s |   6.303 | 25.0ms | 14.0ms | 6.083s | 0.159s

Cleanup ... complete

edit: Just to be clear, it doesn’t necessarily have to do with high load, this could be triggered simply by having several messages sent rapidly. I don’t know how the “async” reception in Python is handled, but if it’s done without threads, it means that if the code in one of the callbacks is held up for some reason, message processing will stall in the meanwhile. “Async” in such circumstances often means that you have a single thread that runs a “message loop” where it processes the connection, and then call various callbacks as things happen. If the callbacks don’t return promptly, it won’t be able to proceed processing the connection, which could lead to one of the sending threads in OH having to wait more than 10 seconds, thus terminating the connection.

But, 10 seconds is a long time, so I’m not sure if this is what actually happens, all I “know” is that the error reported by Jetty indicates that the message can’t be written before the timeout expires.

edit2: I removed my excessive debug logging in OH, and re-ran the benchmark, still using “async send”. Still no disconnects, and better response times this time:

(habapp) PS C:\Repos\Python\habapp> habapp --config C:\Repos\Python\habapp\conf --bench
  _   _    _    ____    _
 | | | |  / \  | __ )  / \   _ __  _ __
 | |_| | / _ \ |  _ \ / _ \ | '_ \| '_ \
 |  _  |/ ___ \| |_) / ___ \| |_) | |_) |
 |_| |_/_/   \_|____/_/   \_| .__/| .__/
                            |_|   |_|
                                        25.12.0
Entry "openhab.connection.buffer" was deleted
Entry "directories.param" moved to "directories.params"
"client_id" in mqtt.connection has been renamed to "identifier"
+------------------------------------------------------------------------------+
|                                    HABApp                                    |
+------------------------------------------------------------------------------+

Bench events .. done!

               |  dur   | per sec | median |  min   |  max   |  mean
      rtt idle | 3.980s |  12.56k |  0.0us |  0.0us | 32.0ms | 79.6us
async rtt idle | 6.015s | 590.847 | 2.00ms | 0.95ms | 7.00ms | 1.69ms

+------------------------------------------------------------------------------+
|                                   openHAB                                    |
+------------------------------------------------------------------------------+

Bench item operations ... done!

            |  dur   | per sec | median |  min   |  max   |  mean
create item | 5.002s |  59.972 | 15.0ms | 10.00ms | 0.129s | 16.7ms
update item | 4.971s |  60.348 | 16.0ms | 12.0ms | 35.0ms | 16.6ms
delete item | 1.922s | 156.066 | 6.00ms | 5.00ms | 10.0ms | 6.41ms

Bench item state update ........ done!

                           |  dur   | per sec | median |  min   |  max   |  mean
                  rtt idle |  15.0s | 104.598 | 9.00ms | 4.98ms | 20.0ms | 9.56ms
            async rtt idle |  15.0s | 111.322 | 8.96ms | 6.00ms | 24.0ms | 8.98ms
             rtt idle item |  15.0s | 202.048 | 5.00ms | 2.96ms | 32.0ms | 4.95ms
       async rtt idle item |  15.0s | 209.815 | 4.96ms | 2.96ms | 17.0ms | 4.77ms
           rtt load (+10x) |  15.1s |  10.351 | 92.0ms | 24.0ms | 0.261s | 96.6ms
     async rtt load (+10x) |  15.0s |  12.108 | 80.0ms | 23.0ms | 0.113s | 82.6ms
      rtt load item (+10x) |  15.0s |  34.886 | 28.0ms | 16.0ms | 51.0ms | 28.7ms
async rtt load item (+10x) |  15.0s |  41.343 | 24.0ms | 14.0ms | 55.0ms | 24.2ms

Cleanup ... complete

Please test the bundle I’ve uploaded in the GitHub issue, it seems to work around the problem, whatever it really is, to me.

I tested it now but seems to keep disconnecting as earlier:

2025-12-25 21:10:30.247 [INFO ] [re.io.websocket.event.EventWebSocket] - WebSocket error: Blocking message pending 10000 for BLOCKING
2025-12-25 21:10:42.243 [INFO ] [re.io.websocket.event.EventWebSocket] - WebSocket error: Blocking message pending 10000 for BLOCKING
2025-12-25 21:11:00.276 [WARN ] [ab.core.internal.events.EventHandler] - Dispatching/filtering event for subscriber 'org.openhab.core.events.EventSubscriber' failed: Blocking message pending 10000 for BLOCKING
java.lang.IllegalStateException: Blocking message pending 10000 for BLOCKING
        at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.lockMsg(WebSocketRemoteEndpoint.java:133) ~[?:?]
        at org.eclipse.jetty.websocket.common.WebSocketRemoteEndpoint.sendString(WebSocketRemoteEndpoint.java:392) ~[?:?]
        at org.openhab.core.io.websocket.event.EventWebSocket.sendMessage(EventWebSocket.java:300) ~[?:?]
        at org.openhab.core.io.websocket.event.EventWebSocket.processEvent(EventWebSocket.java:284) ~[?:?]
        at org.openhab.core.io.websocket.event.EventWebSocketAdapter.lambda$0(EventWebSocketAdapter.java:60) ~[?:?]
        at java.util.concurrent.CopyOnWriteArrayList.forEach(CopyOnWriteArrayList.java:891) ~[?:?]
        at java.util.concurrent.CopyOnWriteArraySet.forEach(CopyOnWriteArraySet.java:425) ~[?:?]
        at org.openhab.core.io.websocket.event.EventWebSocketAdapter.receive(EventWebSocketAdapter.java:60) ~[?:?]
        at org.openhab.core.internal.events.EventHandler.lambda$2(EventHandler.java:169) ~[?:?]
        at java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[?:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) ~[?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[?:?]
        at java.lang.Thread.run(Thread.java:1583) [?:?]


Edit:

Habapp is showing another error though on disconnect:

[2025-12-25 21:12:10,247] [HABApp.connection.openhab]    DEBUG | Websocket close: 1000
[2025-12-25 21:12:10,247] [HABApp.connection.openhab]    DEBUG | Websocket closed: 1000 None
[2025-12-25 21:12:10,248] [HABApp.connection.openhab]    DEBUG | Set error on connection status
[2025-12-25 21:12:10,248] [HABApp.connection.openhab]    DEBUG | Error in Outgoing queue worker: Cannot write to closing transport (ClientConnectionResetError)
[2025-12-25 21:12:10,249] [HABApp.connection.openhab]    DEBUG | DISCONNECTED

That can’t be with the bundle I uploaded, Blocking message pending 10000 for BLOCKING is only thrown when using “sync send”. Are you sure that you stopped the original bundle and started “my version” after dropping it in the addons folder (if that’s what you did)?

I replaced the “original” with yours and restarted openhab but let me try like you specified

Karaf caches the bundles, so that probably won’t work. But, you can either just stop the original and start the other one from the console, or you can “install” the new one with bunde:install/bundle:update. I prefer just stopping and starting, as it’s much easier to revert.

Ok, got it up’n running and so far so good, no errors and no disconnect last 5minutes which never happened before. Will continue monitoring :slight_smile:

Edit: 2h still no errors or disconnects

But this would also congest and lock up HABApp which is not the case.
Also as you said 10s is a long time and HABApp is “idle”.


@lfs_alp5
Still running without disconnects?

What do I have to do to replace the bundle?
Just put it in the addons folder and then run bundle:update org.openhab.core.io.websocket-5.1.0-SNAPSHOT ?

If you want to use bundle:update you should place it anywhere else than in the addons folder, but then it will be installed instead of the current bundle. If you instead just want to test it, stop the existing websocket bundle, put it in the addons folder and then start the “new” bundle that is found in the addons folder.

All I know is that it doesn’t look like the problem is in OHs code. I don’t fully understand what goes wrong, but Jetty complains that the messages aren’t being processed in time - so the problem is somewhere in the websocket handling after it “leaves” OH code. But, if using the async send method “solves” it, I don’t see why we can’t just do that.

Imho it makes sense to try to analyze a little bit more. The logs make me think that HABApp is consuming everything correctly which means that somehow a message locks up.
Maybe I can build a small script which does reproduce the issue without the overhead of the benchmark.

That would be bundle:start/stop ?

Yes.

It’s not that I haven’t tried, but the 10 seconds timeout is hardcoded in Jetty - which means that it’s impossible to debug, because it times out before I can get anywhere. The same with thread dumps - you never get to see what actually blocks what, because the timeout “kills” the connection.

There is no “lockup” in the OH code, I’m pretty sure that I know exactly what change that led to this - sendString() used to be called within a synchronized block, which led to starvation of the event dispatcher threads inside OH when something “stalled” this process. This is why I started refactoring the websocket in the first place.

It might be that synchronized has been put there to work around this very problem originally. I’ve just concluded that I can’t really figure out exactly what happens without recompiling Jetty without the hardcoded timeout - and that’s a bit more than I wish for.

To try to explain a bit what I mean about what the Python code might do, it’s not because the Python code “can’t keep up” because it’s “overworked”. It’s basically about concurrency, or the lack of.

I’m not saying that this is what happens, but I’ll try to illustrate with an example the kind of thing that could cause it: I assume that the Python websocket “receiver” is single threaded, async, and reacts to incoming messages by calling “event handlers”. If the “event handler” code tries to send something back over the websocket as a consequence of something it received, in a synchronous way, the single thread will be prevented from receiving new events. Jetty, while sending in “blocking mode”, might not accept the incoming message until the next outgoing message has been sent. Since the single threaded, receiver can’t process any more incoming messages until the code returns from the event handler, it will just wait for Jetty - and Jetty will wait for it. Until 10 seconds have passed, upon which Jetty closes the connection.

It’s something similar to this I think is the most likely cause. When sending “async” with Jetty instead, Jetty has an outgoing message queue, and don’t depend on being able to complete sending the message first. Thus, it works, because then HABApp is able to send back whatever message it needs to, and Jetty will wait and send the next message when the “line is free”.

I know what a blocked event loop is and I am pretty confident that that’s not the case here. You make many assumptions how the python code works but the logs or the lack thereof indicate that that a blocked event loop is not the case. I’m not seeing a warning that the event loop is blocked, I’m not seeing a warning that the websocket ouput queue might be congested. The logs also show that HABApp keeps happily receiving messages and also sends them in a timely manner.
So there is absolutely zero indication that the issue is actually what you suspect the issue is.
Could it still be the case - sure. Is it likely - no.
So I suggest we’ll wait until we have an easy way to reproduce the issue and then fix it wherever appropriate.

Yes, still no errors or disconnects. Checked all logs and can’t see any problems so far :slight_smile:

I don’t make assumptions that I claim to be “true”, I speculate to try to find some possible explanation. But I agree that my example doesn’t quite fit, I’m merely trying to say that my speculations points to a scenario somewhat “like” this, where Jetty and the Python code somehow “blocks” each other.

But that is in effect what the error on the OH side says.

I’d love nothing more than to figure out exactly what goes wrong, I’m not one that easily accepts not getting to the bottom of something, but: The PR that started all this did very minor changes to the events handler - it was mostly about the log handler. This is why I’m so “convinced” that the trigger for all this must have been the removal of synchronized around sendString(), and that change shouldn’t cause issues, and it certainly can’t cause blocking.

Combined with the knowledge of the change, I spent most of yesterday trying to figure this out, without really getting anywhere. I’m pretty much convinced that whatever happens, takes place after execution has left “OH code”, and the hardcoded timeout prevents the kind of tools I usually use to figure deadlocks/blocking.

I suspect that the “easiest” way to reproduce it is to send a few changes in very rapid succession, where these changes in turn results in events in OH that have to be sent back.

I have attached a small script that reproduces the behavior on the issue.
Tomorrow I’ll try your proposed fix and report back.