diff options
Diffstat (limited to 'chromium/docs/website/site/developers/design-documents/cookie-split-loading/index.md')
-rw-r--r-- | chromium/docs/website/site/developers/design-documents/cookie-split-loading/index.md | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/chromium/docs/website/site/developers/design-documents/cookie-split-loading/index.md b/chromium/docs/website/site/developers/design-documents/cookie-split-loading/index.md new file mode 100644 index 00000000000..2f7c2d93d4e --- /dev/null +++ b/chromium/docs/website/site/developers/design-documents/cookie-split-loading/index.md @@ -0,0 +1,189 @@ +--- +breadcrumbs: +- - /developers + - For Developers +- - /developers/design-documents + - Design Documents +page_name: cookie-split-loading +title: cookie-split-loading +--- + +Currently, the CookieMonster cookie store is initialized by reading in the +cookies from the backing store in a large lump on first access. This is bad for +several reasons: + +* It violates the principle that we shouldn't do blocking operations + on the IO thread. +* It contributes to perceived user startup time in if a session is + being restored (which requires URL accesses, which result in + CookieMonster accesses). +* It contributes to variation in web page loading, as the first web + page takes substantially longer than later ones. + +This problem is tracked in [Issue 52909](http://crbug.com/52909). This document +sketches out a proposed solution to this problem. + +**Requirements** + +* Minimize startup time (including startup time which includes session + restore, i.e. CM access). +* Minimize stalling of individual set/get cookies. + +I believe that these requirements imply: + +* Basic startup cannot be gated on loading all cookies from persistent + backing store. +* Individual page loads cannot be gated on loading the entire cookie + store from persistent backing store. Ideally, individual page loads + would only load the cookies from persistent backing store that are + needed for that page. +* Full persistent store load should be initiated at startup, but in + the background not blocking any processes (as having cookies loaded + in memory will result in absolute minimum latency). + +**Notes on synchronous vs. asynchronous interfaces to the CookieMonster** + +(See [Issue 68657](http://crbug.com/68657)) + +Generally, services on the IO thread provide asynchronous interfaces with +callbacks, to avoid the IO thread becoming a bottleneck in providing services +for other threads. For all requests after the initial one, the CookieMonster +follows this rule and does not block; all information required for all +CookieMonster APIs is in memory. However, the first call to the CookieMonster +triggers loading in of the entire persistent backing store, done on-thread, +which means that the IO thread blocks on disk operations on the first request to +a CookieMonster with a backing store. + +Giving the CookieMonster an asynchronous interface is a large task, not because +of the internal structure of the Cookie, but because of the large number of +callers, all of whom would need to be adapted to the new interface. Issue +<xxx> tracks this task. + +In an ideal world, the current design would not be dependent on async +CookieMonster interfaces. However, a key part of this design is breaking up the +loading of the backing store into indepedent pieces, using Task objects, which +are spawned off asynchronously in the backing in the absence of incoming +reuqests to laod the backing store database. Combining this with a synchronous +interface for those reuqests that need backing store loads would mean doing one +of: + +* Loading data inline (in the IO thread) when it is needed for a + particular reuqest. This would probably mean that all load tasks + would need to be on the IO thread. +* Completely blocking any IO thread activity while waiting for + responses from the DB thread for data load-in. This violates one of + the basic principles of the IO thread (though in theory it's no + worse than what we're doing now). +* Running nested message loops, and carefully tracking which requests + corresponded to which nesting level of the message loop. If request + B were run while request A had an outstanding load message to the DB + thread, this means that request A would be blocked on the completion + of request B (because the stack couldn't unwind until that point). + +None of these options is particularly appealing. Thus it's preferred that this +work be done after the CookieMonster has been converted over to having an async +interface. The rest of this design assumes such an interface. + +\[Note that it would be relatively easy to layer an asynchronous interface (if +not behavior) on top of the current synchronous interface; the async trigger +would wrap the sync call and a PostTask(self) for the callback. This could allow +incremental changes to the various callers. When all callers had been converted, +the sync interface could be removed and the async interface made to behave in a +non-blocking manner. \] + +**High Level Design** + +The cookie monster will get an additional data structure, indexed by domain key +(currently eTLD+1) indicating whether or not a particular domain key's set of +values has been loaded. Incoming requests (either set of get) will trigger the +loading of the domain key(s) required for those requests if they have not yet +been loaded. In these cases, the incoming request will block (as it currently +does if the backing store hasn't been loaded). All actual database accesses will +occur on the DB thread. + +At CookieMonster construction, a background operation will be invoked to load +the entire cookie store. This will run on the database thread, and will break +down into a series of tasks (T) marks each separate task: + +* (T) Get the list of domain keys contained in the persistent backing + store. +* For each key: + * (T) Load all data from that key into memory + * (T -- IO thread) Incorporate that loaded data into the cookie + monster and mark that key as loaded. + +These are broken up into indiviudal tasks both to avoid freezing the DB thread, +and to allow loads of specific keys triggered by particular web page access to +"jump the queue" and get pulled in earlier. + +**Routine Flow Outline** + +The primary routines and data structures involved in this design are sketched +out below. + +[<img alt="image" +src="/developers/design-documents/cookie-split-loading/objects.svg">](/developers/design-documents/cookie-split-loading/objects.svg) + +There are two basic "ropes" (since they're potentially cross-thread) of routine +invocation: the initial spawn of loading of all of the cookies, and the +load-on-demand triggerred by specific requests to the cookie monster. + +The initial spawn occurs from the CookieMonster constructor -> +SQLitePersistentCookieStore::SpawnFullLoad -> +SQLitePersistentCookieStore::BackingStore::SpawnFullLoad. Once the rope has +reached the DB thread, a full list of all the keys in the SQLite DB is loaded +and stored in BackingStore. Then SpawnFullLoad does a self-dispatch (to the DB +thread) of a ChainLoadKeyCookies task for the first key in the list. +ChainLoadKeyCookies will call LoadKeyCookies (which will load in the cookies +associated with that key from the backing store and notify the CookieMonster of +that load) and will then self-dispatch another instance of ChainLoadKeyCookies. +When the last entry in the key list is loaded, ChainLoadKeyCookies will return +without any action. This will (incrementally, allowing other tasks to run on the +DB thread) load all the cookies. + +If a request comes into the CookieMonster while this chain is executing, it will +check all_loaded_ and keys_loaded_ to detemrine that the relevant data for the +request has not been loaded (if the relevant data has already been loaded, it +will execute the in-memory lookup inline as current and call the callback with +the information). It will then register the callback and operation information +into the key->callback mapping. If that is the first registration of that +key, it will call SQLitePersistentCookieStore::LoadKeyCookies, which will +dispatch an invocation to the DB thread of +SQLitePersistentCookieStore::BackingStore::LoadKeyCookies. If LoadKeyCookies +determines that the key requested has already been loaded (race with above +chain), it will simply exit. + +The tricky part of this design is making sure that there are no races that +result in dangling requests. The invariants that guarantee that this doesn't +happen are: + +* The first entry in the keys->callback table is always correlated + with a LoadKeyCookies dispatch to the DB thread. +* That entry/dispatch will only happen if no previous CompleteLoad for + that key has already arrived. +* The only case in which a LoadKeyCookies dispatch will not result in + a later CompleteLoad dispatch is if a previous CompleteLoad dispatch + for that cookie has already been sent. + +So it's possible for a LoadKeyCookies() dispatch to not result in a +CompleteLoad() being sent back, but only if there's already one in transit. +Thus, if a callback is registered in the keys->callback table, it will be +executed with maximum latency round trip to DB thread + queue wait time + load +time. + +**Object Lifetime Relationships** + +\[Will Chan take note :-}.\] + +Both CookieMonster and PersistentCookieStore are RefCountedThreadSafe objects. +The CookieMonster keeps a reference to the PersistentCookieStore in its store_ +pointer; this guarantees that the store lives as long as the Monster does. + +Both SpawnFullLoad and LoadKeyCookies will take as arguments scoped_refptrs on +the CookieMonster which the PersistentCookieStore (or its backing store) will +use for callbacks. These references will guarantee that the CookieMonster stays +alive for the duration of the callback. + +If the CookieMonster needs to be torn down, it will call an abort() routine on +the PersistentCookieStore, which will break the chain load and release the +scoped_refptr<> held on the CookieMonster by that chain load.
\ No newline at end of file |