Process management
Since its first release, Finsemble has provided features to help you manage Chromium render threads for windows rendered by using web technologies. This way you can consolidate multiple windows under a single render thread to save memory, or to allocate dedicated render threads to provide unrestricted access to CPU.
Web browsers don’t typically support this level of thead management because:
They don’t need to because they display only a few windows at a time.
- They have a very different form factor to a smart desktop or typical suite of desktop applications used on financial services desktop; and
- For most browser use cases, there isn't a 3rd party between the user and web application designing the multi-app user experience that can help make decisions about process management. In contrast, there usually is one for a financial services desktop.
Usually the web browser determines how processes will be managed. But Finsemble doesn’t have to rely on the browser engine alone. Instead, Finsemble is configured out-of-the-box to efficiently manage its own render processes as well as the rendering processes of your applications. In addition your desktop assemblers and developers also have direct control over process management to optimize your resource management.
Finsemble gives you the ability to balance performance and resource usage through configuration. You achieve this balance by using process affinities to group several windows into a single renderer process. This approach involves specifying in advance which components should be grouped together.
This tutorial discusses Finsemble's process models and gives you the tools to manage them. To fully understand affinities, let's first look at how web browsers handle window processes.
Finsemble can reduce memory footprint by grouping all windows into the same affinity that created them (that is, the Window Service), but we don't recommend this option. Set an affinity for each of your components instead.
How web browsers manage processes
Single process per window
To understand Finsemble's process model, it's helpful to understand how Chromium—the engine of our container—handles its processes.
The diagram shows Chrome's default process model. Each window is a browser process: in addition to many other responsibilities, the browser process manages communication with all tabs, both visible and hidden. You can think of this process as the brains of Chrome. Each tab usually has a separate renderer process. A renderer process has a single JavaScript event loop. It also handles the actual painting of all of the pixels on the page.
Chrome separates each tab into separate renderer processes for several reasons, but one of the biggest is that it isolates tabs from each other. In other words, tabs don't share fate—tab A can't crash tab B. Generally, the number of tabs maps to the number of renderer processes (though there are exceptions). To understand why this model is crucial, recall the last infinite loop you wrote—your tab crashed but Chrome continued running.
Unfortunately, this process model introduces a larger memory footprint.
To sum up, a single process per window:
- Isolates windows from each other and prevents failures in one instance from affecting others;
- Is resource-intensive.
Sharing renderer process
An alternative to a single process per window is to force windows to share a single renderer process by grouping them into an application. There is no such construct in Electron, but we have created it in Finsemble. In this scenario, Finsemble can open M apps and an app can have N windows. There is only one renderer process per app. By allowing multiple windows to share a single renderer process, the memory usage is dramatically reduced compared to the default process model of Chrome or Electron.
In this scenario, all windows are child windows of a single app. As a result, all windows share a renderer process. As mentioned earlier, each renderer process has a single event loop, single renderer, and so on. This means that window A can impact the timing of code execution and rendering in window B within the same app. In practice, this makes application start-up look like you're knocking over a row of dominoes. The first one has to fall (load and paint) before the second one can.
In addition to generalized sluggishness, this strategy will eventually bump up against hardcoded memory caps imposed by the renderer process.
To sum up, sharing renderer processes:
- Has less memory overhead;
- Results in large renderer processes, which impact responsiveness.
Finsemble is not a Web browser
As we have just seen, a Finsemble-based smart desktop is not a web browser. It has a different set of requirements and challenges for process management. For example:
- A web browser handles a small number of windows, often with many tabs. Finsemble typically manages dozens of visible windows both for small UI components and your business apps. The browser engine (Chromium) deprioritized rendering for non-visible tabs, as does Finsemble. However, in a smart desktop most of the windows are visible all the time, so this deprioritization has only a limited effect.
- A browser usually needs to render a whole web application. In contrast, Finsemble might need to render a large number of small UI components in separate windows that make up a user interface. Chromium usually assigns a unique render thread to each window. However, each thread has a base memory footprint. If we've got lots of UI components and windows that are not all in use all of the time, letting them share render threads can save us a lot of memory.
- A web browser assigns the same level of importance to the rendering of each window. This is often not ideal on a Financial Services desktop, where we might need to express a preference for some applications over others. For example, we might need to ensure a blotter or similar app that is rendering all the time doesn't block another app that needs to react rapidly to market events.
Affinity: Improve performance and reduce memory footprint
Until now, we've seen the all or nothing approach---either we have one process per window or we place all render processes in one window. But we are not limited to these two options, as there is something in-between. You can control which processes are isolated and which are grouped through affinity. In this way, you can strike a balance between performance and memory footprint.
Process affinity
Finsemble uses process affinity, a method of configuring process management that allows you to specify which application windows are allowed to combine on the same render thread to save memory. You can also use process affinity to specify which app windows should be assigned to a dedicated thread ensuring unrestricted access to CPU. Finsemble's built-in UI and Desktop services are already configured to use this system to keep Finsemble's base memory footprint low and to ensure CPU access for key system services.
To maintain the security of render threads, Chromium enforces the 'Site isolation' policy. This policy ensures that windows belonging to different domains or served via different protocols are never combined into the same render thread. (Note that sub-domains and ports are ignored for the purposes of site isolation. As a result, https://subdomain.example.com will be considered to be the same site as https://www.example.com:443). There is an important advantage to this policy. Chromium developers work hard to prevent apps from escaping the Chromium sandbox. However, if a malicious application manages to escape the chromium sandbox , it can’t access the process space in which other websites are running. For more information about site isolation, see Chomium's security documentation.
Configuring affinity
To configure your own apps to use process affinity, set
manifest.window.affinity: "affinityName"
in your app's configuration. Use a common "affinityName"
for apps that you want to cluster together on the same render thread. Due to site isolation, you can use the same group name for apps from different 'sites' and each will form its own grouping.
If you want an app to get its own dedicated render thread, you need to disable affinity for it like this:
manifest.window.affinity: false
Finsemble also supports a default affinity setting for applications to use if they aren't individually configured. To override the default affinity, set
finsemble.servicesConfig.launcher.defaultComponentAffinity: "affinityName"
in your /public/configs/application/config.json file. Setting this value to false disables using affinity by default (unless you configured it individually for an application).
When affinity is combined with BrowserView windows, only the window titlebars will be grouped together. If you want apps' content to share an affinity, BrowserView will need to be disabled by setting:
finsemble.servicesConfig.["Window Manager"].titlebarType: "injected"
in your /public/configs/application/config.json file. The titlebarType can also be configured individually for an application.
Finsemble UI components are configured to use the affinity "systemComponents" by default. You can override this for each component individually in /public/configs/application/UIComponents.json.
Finsemble's built-in desktop services are configured into two groups: the Finsemble micro kernel services (the router, logger, config and system manager services), whose affinity cannot be altered, and kernel and later services, which you can configure via finsemble.servicesConfig.<serviceName>.affinity
. However, FInsemble services are pre-configured for a 'battle-tested' balance of performance and efficiency and you shouldn’t need to adjust them in most use cases.
From Electron to Finsemble
In the past, Finsemble used to rely on Electron for affinity.
Electron used to directly control the allocation of Chromium render threads for windows and would allow the combination of windows from different sites. The process affinity support was removed in Electron 14. But because this feature is hugely important in Finsemble, process affinity with site isolation is now implemented in Finsemble directly. This includes creating relationships between windows from the same site and with the same configured affinity, which allows the Chromium rendering engine to combine their render threads.
With this change, Finsemble 7 introduces a new way to specify process affinity. Until Finsemble 7, we supported a manifest.window.windowType: "application" setting that you could use to disable affinity and ensure an application was assigned to its own render thread. This setting is now deprecated and will be removed in the future. From Finsemble 7 on, set manifest.window.affinity: false
instead to achieve the same effect.
Affinity best practices
When customizing the allocation of renderer processes through affinity, consider these questions:
- Is the app heavy? That is, does it use a lot of memory or CPU?
- Does the work that the app is doing interfere with the performance of other components?
- Is the app on the same domain as other apps that answer yes to the first two questions, and does it need to respond to events very rapidly or without blocking?
If the answer to any of these questions is yes, the app is a good candidate for its own renderer process. In contrast, UI components that don't process significant amounts of data or events in the background (that is, those that are generally only working when the user is interacting with them directly) are good candidates for saving on memory footprint via a shared renderer process allocated via affinity.
See also
More info about Finsemble's Build process.
More info about Finsemble configuration.