fix: class-ify cls implementations#708
Conversation
b8c45f5 to
57ca019
Compare
Codecov Report
@@ Coverage Diff @@
## master #708 +/- ##
==========================================
- Coverage 90.76% 90.74% -0.03%
==========================================
Files 28 30 +2
Lines 1495 1556 +61
Branches 296 304 +8
==========================================
+ Hits 1357 1412 +55
- Misses 59 61 +2
- Partials 79 83 +4
Continue to review full report at Codecov.
|
ofrobots
left a comment
There was a problem hiding this comment.
Still going though, but left some high level comments that will probably need discussion.
| this.logger = logger; | ||
| const uncorrelated: RootContext = {type: SpanDataType.UNCORRELATED}; | ||
| const untraced: RootContext = {type: SpanDataType.UNTRACED}; | ||
| const useAH = config.mechanism === 'async-hooks' && asyncHooksAvailable; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| `propagation is not available in Node ${process.version}.`, | ||
| 'Falling back to using async-listener.'); | ||
| } else { | ||
| this.logger.warn( |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| // special case where _all_ requests are explicitly not being traced, | ||
| // so return UNTRACED_SPAN to be consistent with that. | ||
| return UNTRACED_SPAN; | ||
| disable(): void { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
ofrobots
left a comment
There was a problem hiding this comment.
There is more to look at, but I will leave it here for the initial round.
| @@ -1,5 +1,5 @@ | |||
| /** | |||
| * Copyright 2015 Google Inc. All Rights Reserved. | |||
| * Copyright 2018 Google LLC | |||
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| * path in which they were attached to the EventEmitter object. | ||
| * @param ee The EventEmitter to bind. This instance will be mutated. | ||
| */ | ||
| patchEmitterToPropagateContext<T>(ee: EventEmitter): void; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| private hook: asyncHooksModule.AsyncHook; | ||
| private enabled = false; | ||
|
|
||
| constructor(defaultContext: Context) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| * module. | ||
| */ | ||
| export class AsyncHooksCLS<Context extends {}> implements CLS<Context> { | ||
| private current: {value: Context}; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| runWithNewContext<T>(fn: Func<T>): T { | ||
| const oldContext = this.current.value; | ||
| this.current.value = this.defaultContext; | ||
| const res = fn(); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| const contextWrapper = function(this: {}) { | ||
| const oldContext = current.value; | ||
| current.value = boundContext; | ||
| const res = fn.apply(this, arguments) as T; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
|
|
||
| const EVENT_EMITTER_METHODS: Array<keyof EventEmitter> = | ||
| ['addListener', 'on', 'once', 'prependListener', 'prependOnceListener']; | ||
| const WRAPPED = Symbol('context_wrapped'); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| } | ||
| }); | ||
| } | ||
| } No newline at end of file |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| * | ||
| * CLS stands for continuation-local storage. | ||
| */ | ||
| export interface CLS<Context extends {}> { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| /** | ||
| * Enables this instance. | ||
| */ | ||
| enable(): void; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
52e7e65 to
03bc2df
Compare
| export class TraceCLS implements CLS<RootContext> { | ||
| private currentCLS: CLS<RootContext>; | ||
| // CLSClass is a constructor. | ||
| // tslint:disable-next-line:variable-name |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| */ | ||
| readonly rootSpanStackOffset: number; | ||
|
|
||
| constructor(logger: Logger, config: TraceCLSConfig) { |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| return cls.createNamespace(TRACE_NAMESPACE); | ||
| } | ||
| isEnabled(): boolean { | ||
| return this.enabled; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| const EVENT_EMITTER_METHODS: Array<keyof EventEmitter> = | ||
| ['addListener', 'on', 'once', 'prependListener', 'prependOnceListener']; | ||
| // A symbol used to check if a method has been wrapped for context. | ||
| const WRAPPED = Symbol('@google-cloud/trace-agent::AsyncHooksCLS::WRAPPED'); |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| import * as semver from 'semver'; | ||
|
|
||
| import {AsyncHooksCLS} from './cls/async-hooks'; | ||
| import {AsyncListenerCLS} from './cls/async-listener'; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| } | ||
|
|
||
| patchEmitterToPropagateContext(ee: EventEmitter): void {} | ||
| } No newline at end of file |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| export class UniversalCLS<Context> implements CLS<Context> { | ||
| private enabled = false; | ||
|
|
||
| constructor(private defaultContext: Context) {} |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
| * limitations under the License. | ||
| */ | ||
|
|
||
| import * as clsModule from 'continuation-local-storage'; |
This comment was marked as spam.
This comment was marked as spam.
Sorry, something went wrong.
Summary
This PR refactors the CLS implementations as classes implementing a common interface. There are a number of implementations offered:
UniversalCLStreats all code as running in the same continuation.AsyncHooksCLSusesasync_hooksto propagate context within a continuation, with no semantic changes w.r.t the original implementation.AsyncListenerCLSusesasync-listenerto propagate context withasync-listener(a lacontinuation-local-storage), also with no semantic changes compared to the original implementation.TraceCLSis a meta-implementation that wraps either of the Async* mechanisms, based on its config object. When enabled it uses eitherAsyncHooksCLSorAsyncListenerCLSwith a default value (when there is no available context) set to{ type: UNCORRELATED }. When disabled, it usesUniversalCLSwith the universal value set to{ type: UNTRACED }. I do not expect this to be a semantic change, as this roughly describes the current implementation today (that when the Trace Agent is disabled, all spans areUNTRACEDphantom spans).The reasons for making such a change is as follows:
continuation-local-storageNamespaceinterface, but we should de-couple the two implementations by instead introducing our own common interface.UniversalCLSmay be useful in environments where CLS doesn't matter.Change Overview
Bold denotes a new file, and
strikethroughdenotes a removed file.cls.ts: Formerly exposed functions to manipulate ac-l-sNamespace(implementation of which is dependent onGCLOUD_TRACE_NEW_CONTEXT).cls-ah.ts: Formerly an implementation of theNamespaceinterface fromcontinuation-local-storageusing async_hooks.TraceCLSas described above.CLSinterface definition.CLSinterface based on async_hooks.CLSinterface based onasync-listener.UniversalCLSas described above.Reading the environmental variable
GCLOUD_TRACE_NEW_CONTEXTis now done inindex.ts.