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**