github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/doc/context.md (about) 1 2 # A Note On The Various "Contexts" and "LoginState" 3 4 We are in the midst of wide and slow-moving code reorgnization with two major goals: 5 6 1. To standardize as much code as possible to take one of three "context" objects, always 7 as a first argument: (a) Go's standard `context.Context`; (b) our rollup `libkb.MetaContext`; 8 or (c) our chat-specific context `chat.ChatContext`. We want to eliminate all cases 9 of functions taking multiple contexts, or contexts not in the first parameter slot, etc. 10 2. To retire `libkb.LoginState`, `libkb.LoginContext`, and `libkb.Session`, and to migrate 11 their roles into `libkb.ActiveDevice`. 12 13 The goal of `libkb.MetaContext` is to provide both thread-local (see `.Ctx` and `.activeDevice`) and global context (see `.g`). During signup, login or provisioning, we can store a thread local 14 version of the `ActiveDevice` in the `MetaContext` so that the various login and provisioning 15 routines can act upon it before exposing it to the rest of the program, since it's still provisional 16 until login completes. Once the `ActiveDevice` becomes official, then all threads can access it 17 via `GlobalContext`. 18 19 There's a ton of changes we'd have to make in the code to achieve these goals, and 20 we'd like to proceed in small piecemeal steps so that we can shake out any bugs 21 as we go. 22 23 # New Rules on Passing Contexts 24 25 After this migration is done, we'll have the following rules on passing contexts through 26 Go code 27 28 1. You can pass a `libkb.MetaContext` only as a first argument; if you do, you can 29 pass no other contexts. 30 1. You can pass a `context.Context` only as a first argument, and optionally a `libkb.GlobalContext` 31 as a second argument, but no other contexts. 32 1. You can pass a `libkb.GlobalContext` as a first argument, but if so, no other contexts. 33 1. You can never pass `libkb.GlobalContext` as a third or higher argument. 34 1. If a particular method is on a `libkb.Contextified` receiver (has a 35 `libkb.GlobalContext` dependency-injected), and has a `libkb.GlobalContext` or 36 `libkb.MetaContext` passed in, then use the `libkb.GlobalContext` from the 37 argument, as we intend to sunset `libkb.GlobalContext`-dependency injection. 38 1. In chat, you can pass a `globals.Context` as a first argument, or as a second argument 39 behind a `context.Context`, but never as a third argument or higher. 40 41 We're not going to get there overnight, but all code should obey these rules going forward, 42 and if possible, you should refactor code to be aligned with these rules. 43 44 # History 45 46 We have a long and sordid history here, and it might be worth explaining a little bit 47 of what happened before we describe the strategy for going forward. When we first 48 started this project, the Go standard `context.Context` hadn't fully formed yet, 49 so we didn't incorporate it. Instead, we had a notion of `GlobalContext` which applies to 50 all threads. At first, all threads accessed this global context via a global variable `G`, 51 but that strategy was terrible for many reasons, and made testing multiple instances of 52 Keybase in the same address space near-impossible. Thus, we embarked upon a lengthy crusade 53 to retire to `G` variable and use a combination of dependency-injection and just passing `G` 54 wherever we could. 55 56 Around the same time, we started to adopt the `context.Context` standard, especially 57 for logging with request-specific tags (useful for debugging). These attempts were sometimes 58 at odds, so we would up with an inconsistent ordering and placement of these contexts 59 when passed to functions. Also, though were finally able to retire `G`, we did not succeed 60 in fully threading `context.Context`s through the code; nor did we finish the project to always 61 use `context.Context`-aware versions of logging functions. 62 63 In addition, we've long had the LoginState/Account/LoginContext/Session family of objects 64 to manage the user's logged-in state, and to shepherd the user through signup and device 65 provisioning. We've experienced growing pains and bugs around the current configuration 66 and long for a simplification. In particular, we're not happy with the Go-channel-based 67 synchronization primitives at the heart of the state maintenance here, since it's easy 68 to hit deadlocks hidden behind layers of abstraction. Instead, we want a simple lock-based 69 model, where those locks are only held briefly, never during a network request (let's say). 70 71 # Migration Strategy 72 73 ## Step 1: Use NIST Tokens for Session Establishment 74 75 It used to be the case we needed the exclusive lock over Account/LoginContext 76 to make an API call, since it needed the user's session cookie (and CSRF 77 token), and it was stored there. This setup made it very easy for API calls to 78 fight over this locked resource and to stall, especially on application 79 foregrounding or resumption from a long sleep. So the solution here is to 80 authenticate a client to the server just based on a signature that the user 81 can cook up with just her/his public key. Now, API calls are no longer 82 dependent on Account/LoginContext, and instead depend on `ActiveDevice`, with 83 the exception of provisioning and signup (i.e., before proper device keys are 84 established). 85 86 Status: **completed** 87 88 ## Step 2: Propagate MetaContext from libkb outward 89 90 ### Step 2a: Replace LoginContext with a wrapper MetaContext (Part 1) 91 92 93 - Start with `LoginState`-related functions and propagate outwards. Cover `ActiveDevice`, 94 `PerUserKey`, and bubble up into `engine/` too, but only as necessary. 95 96 Status: **completed** 97 98 ### Step 2b: Move `engine.Context` into `libkb.MetaContext` 99 100 - And then change all `engine/` code to take only the `libkb.MetaContext` 101 102 Status: **completed** 103 104 ### Step 2c: Replace LoginContext with a wrapper MetaContext (Part 2) 105 106 - Continue with `stellar/` and `ephemeral/` to replace those functions that take 107 both `context.Context` and `*GlobalContext` to take only `libkb.MetaContext`. 108 109 Status: **half-done** 110 111 ## Step 3: Retire LoginState 112 113 Once we get to this point, things are a little less clear. The advantage of having 114 done step 2 first is that a lot of times, we check for a thread-local `LoginContext` 115 and then fallback to one that we grab from the global state. A lot of code is 116 duplicated to handle these two cases, since the access pattern is different. 117 One strategy here might be to move to a `LoginContext`-like object that can be safely 118 copied, so it's no longer necessary to operate on it from outside of a closure. 119 120 We don't need to do this all at once, so we proceed engine-by-engine: 121 122 1. `engine.Signup` [#11663](https://github.com/keybase/client/pull/11664) 123 1. `engine.LoginWithPaperKey` [#11676](https://github.com/keybase/client/pull/11676) 124 1. `engine.LoginProvisionedDevice` [#11693](https://github.com/keybase/client/pull/11693) 125 1. `engine.Login` [#11721](https://github.com/keybase/client/pull/11721) (and others) 126 1. `engine.LoginLoad` --- done 127 1. `engine.LoginProvision` --- done 128 1. `engine.LoginOffline` --- done 129 1. `engine.LoginOneshot` --- done 130 131 Status: **completed**