CEF and WebSockets

Having problems with building or using CEF's C/C++ APIs? This forum is here to help. Please do not post bug reports or feature requests here.

CEF and WebSockets

Postby danmontz » Thu Apr 16, 2020 4:00 pm

Has anyone had any issues with receiving websocket messages in a timely manner?

I'm using the following version of CEF/Chromium running on a Linux distro.

Code: Select all
#define CHROME_VERSION_MAJOR 76
#define CHROME_VERSION_MINOR 0
#define CHROME_VERSION_BUILD 3809
#define CHROME_VERSION_PATCH 132


I have created an application (based on cefsimple) running on Linux that runs at
video frame rates (59.94Hz). I am using OSR with the external_begin_frame_enabled
set to true so that I can get OnPaint calls and control when the browser's BeginFrame
occurs.

When my top-of-frame occurs, I'm calling the SendExternalBeginFrame function on each
of the created browsers (in my case, I only have 1 browser).

Everything seems to be working great, until I started looking into my received websocket messages.

The HTML/JavaScript that I'm loading makes a connection to a local WebSocket server.
This local websocket server sends 2 ASCII messages (data length is 51 bytes) very
quickly back to back at top-of-frame before the Browser's SendExternalBeginFrame() is called.
I have used tcpdump to validate that these message are sent from the websocket server as
expected (around 100 us between packets).

I'm using performance.now() in JavaScript to determine when the websocket messages are
being received. I'm finding that they are spaced out by 8 ms most of the time.

Code: Select all
CEF Data:
       Data                                              WS.bufferedAmount performance.now() (ms)
      ------------------------------------------------- ----------------- ----------------------
WS RX: { "command" : 99, "tickid" : 26941977, "id" : 0 } buffered: 0       7689.139999973122
WS RX: { "command" : 99, "tickid" : 26941977, "id" : 1 } buffered: 0       7698.870000021998


I have also done this same testing with Chromium (v74) but have not seen any issues.
Code: Select all
Chromium Data:
       Data                                              WS.bufferedAmount performance.now() (ms)
      ------------------------------------------------- ----------------- ----------------------
WS RX: { "command" : 99, "tickid" : 26987826, "id" : 0 } buffered: 0       8057.359999977052
WS RX: { "command" : 99, "tickid" : 26987826, "id" : 1 } buffered: 0       8057.504999975208


Why are these being received over the JavaScript WebSocket interface around 8 ms apart with CEF?
Is there something in CEF that throttles the handling of websocket messages? Is there
anyway to get these to be handled like they are for Chromium?

Thanks in advance,
Dan
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Re: CEF and WebSockets

Postby magreenblatt » Thu Apr 16, 2020 10:18 pm

How does it behave in the CEF sample app and Chrome at the same version? How does it behave in the CEF sample app at the version that you are using? How are you running the message loop in your app? How long does the OnPaint method take to execute in your app?
magreenblatt
Site Admin
 
Posts: 10923
Joined: Fri May 29, 2009 6:57 pm

Re: CEF and WebSockets

Postby danmontz » Fri Apr 17, 2020 11:27 am

Q1: I'm currently cross compiling CEF v74 which is the Chromium version I compared against. This takes many hours.
Q2: I don't understand your question. I have added a sequence diagram below for some clarity.
Q3: I'm just using CefRunMessageLoop
Q4: My OnPaint method is measured at ~200us. It's really fast basically just small memcpy.

In the picture below, if I add a slight delay (DELAY HERE) before sending the tick to the thread in my modified cefsimple app, then it appears that the tick messages are processed in the current paint cycle as expected. I'm starting to wonder if the CEF BeginFrame processing will only process websocket messages that are waiting when the SendExternalBeginFrame is called. If it arrives after the browser has started begin frame processing, then it is buffered until the next begin frame. I don't know how to confirm this, but I'm playing around with some ideas.

A picture's worth a 1000 words:
Code: Select all
                                 App A's        slightly                JavaScript             
                                 WebSocket      modified                running in
              App A              Server         cefsimple               CEF context            CEF
            +----+---+         +-----+-----+  +-----+------+           +------+-----+      +----+-----+
  V-sync         |                   |              |                         |                 |
+--------------->+                   |              |                         |                 |
                 | Send tick (id 0)  |              |                         |                 |                 |
                 +------------------>+              |                         |                 |
                 | Send tick (id 1)  |              |                         |                 |
                 +------------------>+              |                         |                 |
      DELAY HERE | Tick (semop)      |              | Thread waiting on Tick  |                 |
                 +--------------------------------->+                         |                 |
                 |                   |              | Browser->GetHost()->SendExternalBeginFrame();
                 |                   |              +------------------------------------------>|
                 |                   |              |                        *** Tick (id 0)    | +--+
                 |                   |              |                         | msg received    |    |
                 |                   |              |                         | over WS         |    |
                 |                   |              |                         |                 |    |
                 |                   |              |                         |                 |    +-- 8 ms
                 |                   |              |  OnPaint                |                 |    |
                 |                   |              |<------------------------------------------+    |
                 |                   |              |                         |                 |    |
                 |                   |              |                        *** Tick (id 1)    | +--+
                 |                   |              |                         | msg received    |   
                 |                   |              |                         | over WS         |
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Re: CEF and WebSockets

Postby danmontz » Mon Apr 20, 2020 3:39 pm

When offscreen rendering is enabled, is there any additional buffering performed between the browser and render processes? Is the render image double buffered?
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Re: CEF and WebSockets

Postby magreenblatt » Mon Apr 20, 2020 4:31 pm

danmontz wrote:When offscreen rendering is enabled, is there any additional buffering performed between the browser and render processes? Is the render image double buffered?

Yes, there is additional buffering to store the image in the main process memory (versus writing directly to a GPU surface). See documentation at https://www.chromium.org/developers/des ... m-graphics
magreenblatt
Site Admin
 
Posts: 10923
Joined: Fri May 29, 2009 6:57 pm

Re: CEF and WebSockets

Postby danmontz » Mon Apr 20, 2020 4:47 pm

Thanks, I'll read the the plethora of documentation. Is there a way to get to the Render process's image buffer instead of waiting for the OnPaint in the Browser process? This would allow me to get rid of the extra frame of latency that I'm seeing. Thanks in advance.
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Re: CEF and WebSockets

Postby magreenblatt » Mon Apr 20, 2020 5:50 pm

The most performant option currently is this PR.
magreenblatt
Site Admin
 
Posts: 10923
Joined: Fri May 29, 2009 6:57 pm

Re: CEF and WebSockets

Postby danmontz » Fri Apr 24, 2020 12:09 pm

So I have perused through the documentation but haven't been able to find exactly what I'm looking for. Lots of interesting low-level information!

I have been running experiments to prove to myself that there is one frame of latency inherent in the design. Here's what I have done:

I am keeping a count of the number of times that I have manually sent BeginFrame to the browser. I am calling frame->ExecuteJavaScript before and after the call to SendExternalBeginFrame(). In the ExecuteJavaScript, I am setting the innerHTML of 2 h1 elements (call them 'before' and 'after'). Next, I'm capturing all images in the OnPaint callback.

From the data that I gathered, here's what I see:
ExecuteJavaScript(set before.innerHTML = 20)
SendExternalBeginFrame()
ExecuteJavaScript(set after.innerHTML = 20)
OnPaint <-- this OnPaint image buffer contains the value of 19 in the before and after HTML elements

ExecuteJavaScript(set before.innerHTML = 21)
SendExternalBeginFrame()
ExecuteJavaScript(set after.innerHTML = 21)
OnPaint <-- this OnPaint image buffer contains the value of 20 in the before and after HTML elements

Is this expected? If so, where is the extra frame of latency occurring at?

I'm currently using CefRunMessageLoop() for my message loop. Would integrating the message loop with my calls to SendExternalBeginFrame thread have any impact? I'm not quite sure how the CEF Message Loop impacts the BeginFrame/OnPaint mechanism.

Thanks in advance!
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Re: CEF and WebSockets

Postby magreenblatt » Fri Apr 24, 2020 12:24 pm

There are a large number of things happening asynchronously in different processes. The compositor is returning via OnPaint whatever contents are available at the BeginFrame deadline -- it doesn't know or care about what the JavaScript is doing at the same time. On the JavaScript/renderer side you can use requestAnimationFrame to be notified after the next layout/render pass is complete. So, putting those two things together, if you implement your own synchronization point by using the JavaScript requestAnimationFrame call to trigger a BeginFrame (e.g. by using a bound callback to the main process) then you might get timing results closer to what you're seeking.
magreenblatt
Site Admin
 
Posts: 10923
Joined: Fri May 29, 2009 6:57 pm

Re: CEF and WebSockets

Postby danmontz » Fri Apr 24, 2020 2:28 pm

Thanks for the reply! Yes, I get that there are many things happening asynchronously, but your suggestion seems counter-intuitive to me. The BeginFrame is the event that triggers the JavaScript/Paint cycle to the Render process. If the Render process is not receiving BeginFrames, it is mostly sitting idle (remember, I'm using external_begin_frame_enabled = true). My HTML is using RAF so it is slaved to the BeginFrame (aka V-sync) event as well.

I'll continue to investigate this further.

Do you have more details on exactly what the CEF message loop is doing? Is it merely handling IPC messages and IO? Would I expect a different behavior than what I'm seeing if I integrated the message loop into my own tick mechanism?
danmontz
Techie
 
Posts: 28
Joined: Thu Apr 13, 2017 4:11 pm

Next

Return to Support Forum

Who is online

Users browsing this forum: Majestic-12 [Bot], Peter and 23 guests