New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Font system redesign #32033
Comments
The `FontContextHandle` was really only used on FreeType platforms to store the `FT_Library` handle to use for creating faces. Each `FontContext` and `FontCacheThread` would create its own `FontContextHandle`. This change removes this data structure in favor of a mutex-protected shared `FontContextHandle` for an entire Servo process. The handle is initialized using a `OnceLock` to ensure that it only happens once and also that it stays alive for the entire process lifetime. In addition to greatly simplifying the code, this will make it possible for different threads to share platform-specific `FontHandle`s, avoiding multiple allocations for a single font. The only downside to all of this is that memory usage of FreeType fonts isn't measured (though the mechanism is still there). This is because the `FontCacheThread` currently doesn't do any memory measurement. Eventually this *will* happen though, during the font system redesign. In exchange, this should reduce the memory usage since there is only a single FreeType library loaded into memory now. This is part of servo#32033.
I think it's an interesting idea and I have some questions, mostly about how this would play out when running in multiprocess mode. The current proposal does not define how it would be impacted by process boundaries. The current font cache is found alongside the constellation in the "main process" of Servo(what other engines refer to as the chrome process I think), whereas the users of the font cache would be partitioned by a I can imagine a hierarchical structure, where we keep the core font mechanism inside the "main browser process", alongside the constellation, as well as the resource loading mechanism it uses, but perhaps supplement it with local per-content-process caches which would follow something alongside the proposed design and allow for easy sharing between threads within a single content-process, but I wonder how many threads would benefit from this, after the removal of individual threads for layout(with #31346). Lastly, I wonder if we should not rather partition further the current font cache, for security or privacy, for example per , meaning we also need to think about when not to share data across boundaries, such as only on a "per BC group"(Something like this will have to eventually happen for the HTTP cache, see whatwg/fetch#904). |
The `FontContextHandle` was really only used on FreeType platforms to store the `FT_Library` handle to use for creating faces. Each `FontContext` and `FontCacheThread` would create its own `FontContextHandle`. This change removes this data structure in favor of a mutex-protected shared `FontContextHandle` for an entire Servo process. The handle is initialized using a `OnceLock` to ensure that it only happens once and also that it stays alive for the entire process lifetime. In addition to greatly simplifying the code, this will make it possible for different threads to share platform-specific `FontHandle`s, avoiding multiple allocations for a single font. The only downside to all of this is that memory usage of FreeType fonts isn't measured (though the mechanism is still there). This is because the `FontCacheThread` currently doesn't do any memory measurement. Eventually this *will* happen though, during the font system redesign. In exchange, this should reduce the memory usage since there is only a single FreeType library loaded into memory now. This is part of servo#32033.
@gterzian This is a great point. The other consideration for the multiprocess case is sandboxing. I think the solution here is to have the |
The `FontContextHandle` was really only used on FreeType platforms to store the `FT_Library` handle to use for creating faces. Each `FontContext` and `FontCacheThread` would create its own `FontContextHandle`. This change removes this data structure in favor of a mutex-protected shared `FontContextHandle` for an entire Servo process. The handle is initialized using a `OnceLock` to ensure that it only happens once and also that it stays alive for the entire process lifetime. In addition to greatly simplifying the code, this will make it possible for different threads to share platform-specific `FontHandle`s, avoiding multiple allocations for a single font. The only downside to all of this is that memory usage of FreeType fonts isn't measured (though the mechanism is still there). This is because the `FontCacheThread` currently doesn't do any memory measurement. Eventually this *will* happen though, during the font system redesign. In exchange, this should reduce the memory usage since there is only a single FreeType library loaded into memory now. This is part of #32033.
Perhaps |
Yep, I meant |
Couple of more questions, I have looked further into the existing system, so I hope my questions are now more specific:
With "the UI-process" do you mean the content-process(where script and layout runs)? If so that would mean running multiple font threads, one per content-process, which I think would defeat the purpose of increasing sharing. I would rather keep one central Edit: I've just become aware the Regarding the specific problems that the re-design is meant to address:
Finally, is the proposed I do see a problem with the current |
The UI process is another name for the chrome process. "Chrome process" is Chrome/Chromium parlance while "UI process" is the term that WebKit uses. I think we ultimately need three tiers of data structure for fonts:
I sort of addressed this above.
We don't have a layout thread for each layout, but layout does create worker threads and currently there is a
It might be possible to handle it in the current design, but I think we should rethink things to unpack as much of our technical debt as possible. In my proposal this means a data structure that just concerns itself with caching font data. I think that will simplify a lot of things, because then all the other font data structures just have to care about holding
I think this can be even simpler if we have a per-layout
The big difference is that there is one
This is an interesting question. Regarding your point about blocking script -- this is already the case and even was when we had the layout thread, because script always blocked on the layout thread layout. There was never any parallelism between script and layout (apart from some really minor things). I think we can never do a layout until we have at the very least loaded the system font list and loaded fonts necessary for a layout (even if falling back from in-process web fonts). Doing a layout before this happens will lead to flashes of badly laid out pages. It's better to show nothing than do that, I think. That said, once those fonts are loaded once, they should probably never be synchronous calls to use them ever again (unless the system fonts change while Servo is running). Maybe we can look into pre-populating the per-content-process |
Thanks for clarifying, it makes mostly sense to me so far: the three-tier layered structure seems to me make sense, although the need for one 'LayoutFontStore' per layout and specific for web fonts is not completely clear yet, but I guess it will be. Is it because the lifecycle of web fonts is different and to facilitate their unloading? |
The reason that we need a |
Ah, there's another issue that I wanted to bring up related to sandboxing as well. It seems impossible to send system font data over IPC or shared memory on macOS, because of the way the CoreText APIs work. Basically system TTC files need to be loaded from disk by CoreText for it to work properly. Due to this, we'll need to continue loading system fonts using CoreText, poking a hole in the sandbox for them. This is what Chromium does and I suspect we'll need to do the same thing. |
I've looked into this a little more and I have some comments:
|
Regarding font handles, Servo's requirements are very tricky due to sandboxing and IPC. I think there's no hope of using something like |
This would be nice to do, but fonts and web layout are very intertwined. The first priority should be that things work correctly. The details of the |
Current system
Currently Servo uses a single
FontCacheThread
, which is responsible for loading the system font list and also storing the available web fonts. In addition, every layout thread stores aFontContext
which caches all font data structures. When the data structure is not in the thread-localFontContext
cache, IPC messages are used to request the data from theFontCacheThread
.FontCacheThread
The
FontCacheThread
is a long-running background thread that is reponsible for reading the sytem font lists and storing data for web fonts. It also manages WebRender font primitives, sending them to the layout-thread-specificFontContext
. Some of the data structures it manages:FontIdentifier
: Unique platform-specific identifier for a source for creating a font face. For local fonts it contains either a path to a local font (or data used to find a path) and possibly a variation index. For web fonts, this is a URL. MultipleFontTemplate
s might have the sameFontIdentifier
.FontDescriptor
: Describes a font's properties (weight, stretch, style).FontTemplateData
: Platform-specific data for a font. ContainsArc<Vec<u8>>
bytes,FontIdentifier
and in the case of MacOS aCTFont
cache.FontTemplate
: "All the information needed to create font instance handles." This includes aFontIdentifier
, aFontDescriptor
, and theFontTemplateData
(both strong and weak references).FontContext
The
FontContext
is the per-layout-thread store of instantiated fonts and cachedFontTemplate
s. When a layout thread is doing font matching, it asks theFontContext
to find templates that match a given descriptor. These requests are forwarded to theFontCacheThread
via IPC and the responses are cached. When sendingFontTemplate
s toFontContext
theFontCacheThread
does not serialize font data, as that would be too expensive. Instead it serializes all of the members apart from the data intoSerializedFontTemplate
and then sends the data via a direct write into an IPC channel. TheFontContext
is also responsible for turningFontTemplate
s into concreteFont
s which hold platform-specific data structures via theirFontHandle
member and other instance information.Problems with the current system
Web fonts are global: Web fonts are associated with family names in the
global
FontCacheThread
. Here's an example:font-iframe-1.html
:font-iframe-2.html
:If you load the first page and then reload, both iframes will use the same font, even though it should only be available on the first page.
Font data is copied to each layout thread: After Change font serialisation to use IpcBytesReceiver #28736, font data is no longer serialized and deserialized via IPC, but it is still copied to each layout thread and cached there. There is no sharing of data between threads.
Font data is loaded more than once for fonts that share files: When more than one font is stored in the same file, the file is read from disk multiple times, even if the font cache has just read that file. This is because font data is stored on
FontTemplate
, but fonts with different names that share files have differentFontTemplate
s.Font data for web fonts is never unloaded: Font data for web fonts is stored in the the global font cache thread, so it is never unloaded -- even when moving between pages.
Redesign
The idea of the new system is that all font data structures will be both
Sync
andSend
. There will still be a font cache thread for long-running operations such as loading platform font lists and sanitizing web fonts, but in general there will be global stores of font information protected byRwLock
, much like how the HTTP cache works in Servo. In the optimal path, many threads can have access to font data without having to do IPC with the font cache thread at all, only blocking when more than one thread is trying to lazily construct the same font resource at the same time.FontDataStore
There will be a global
FontDataStore
which keeps a handle on the loaded byte data of fonts. This will include the bytes of both system fonts (keyed by file path) and web fonts (keyed by URL). The font data store will keep data for fonts that are in use and also keep a an LRU cache to enable font data sharing. When font data no longer has outside reference, font data will be kept in memory by the cache. If a font with no outside references is evicted from the cache its data will be freed. The reason this is stored separately fromFontTemplate
is to ensure thatFontTemplate
s sharing a file on the system or URL can use the same font data.FontStore
A font store will store
FontTemplate
s for a list of fonts available, much liketheFontCacheThread
does now. There will be a single sharedGlobalFontStore
used for system font list and also oneFontStore
perLayout
that is used to store web fonts. Each layout will have a separateFontTemplate
for web fonts, but those templates will share the actual byte data through theFontDataCache
. EachFontStore
will also be protected by aRwLock
so that it can be accessed by multiple threads without having to use IPC. Operations that mutate theFontStore
will block all access to theFontStore
.FontTemplate
The design of
FontTemplate
will remain very similar to the current design, butFontTemplateData
will be integrated into theFontTemplate
itself. In addition,FontTemplate
s will be wrapped inArc<RwLock<FontTemplate>>
which will allow sharing templates across threads and lazily initializing data and desciptors from any place in the code. There will no longer need to be strong and weak references to font data, which are always strong currently in any case, because dropping the layout-specificFontStore
should automatically clean up page-specific font data. In the future, it may make sense to cacheFontStore
s for back and forward navigation.Font
In general,
Font
does not change very much either. The biggest change here is thatFontHandle
will become bothSync
andSend
andFontRef
will becomeArc<RwLock<Font>>
so it can be shared across threads. If any locking needs to happen onFontHandle
due to platform threading issues, that will happen in the platform-specificFontHandle
code. In general, platform font data structures can be used across threads 12.Rough plan
FontTemplateData
directly intoFontTemplate
and wrapFontTemplate
in a shared mutability and ownership data structure.FontTemplateData
#32034FontDataStore
to enable font data sharing across threads.FontHandle
intoFont
and renameFontHandle
toPlatformFont
.FontContextHandle
#32038 (part one)Font
bothSend
andSync
and wrap them inArc
instead ofRc
so they can be shared across threads and turnFontContext
into a per-layoutFontStore
and wrap it inRwLock
.FontContext
thread-safe and share it per-Layout #32205GlobalFontStore
data structure that can be accessed from multiple threads protected viaRwLock
.FontStore
responsible for handling completed web font loads and storing templates for them.Footnotes
"All individual functions in Core Text are thread-safe. Font objects (CTFont, CTFontDescriptor, and associated objects) can be used simultaneously by multiple operations, work queues, or threads." from https://developer.apple.com/documentation/coretext/ ↩
"[Since 2.5.6] In multi-threaded applications it is easiest to use one FT_Library object per thread. In case this is too cumbersome, a single FT_Library object across threads is possible also, as long as a mutex lock is used around FT_New_Face and FT_Done_Face." from https://freetype.org/freetype2/docs/reference/ft2-library_setup.html. ↩
The text was updated successfully, but these errors were encountered: