gctx

package module
v0.0.0-...-0c57ec7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2022 License: MIT Imports: 5 Imported by: 0

README

gctx

Build Status Go Report Card GoDoc

Implementation of a goroutine context (gctx).

TL;DR
Set a goroutine context with gctx.Set(ctx) and retrieve it from anywhere in the same or a child goroutine with gctx.Get().

A gctx is a context.Context that is neither global, nor in the local function scope, but a property of a specific goroutine. It can only be created and retrieved by code being executed within the according goroutine. The gctx can be set with gctx.Set():

ctx := context.WithValue(context.Background(), myCtxVal{}, "foobar")
go func() {
    gctx.Set(ctx)
    ...
    /* some code that eventually calls doSomething() */
    ...
}()

Then, from any function called within the same or a child goroutine this gctx can be retrieved with gctx.Get():

func doSomething() {
    ...
    ctx := gctx.Get()
    v := ctx.Value(myCtxVal{})
    log.Printf("ctx: %v", v) // prints "ctx: foobar"
    ...
}

Why

The Go language decided for good reasons that goroutines are anonymous by design. So there is no ID or any goroutine-local storage exposed. (Well, almost, see below.) It is advised to always use a context.Context as the first parameter in all functions that require knowledge about a specific context.

However, there are also good reasons to have such a goroutine specific context generally available. The most common reason is logging:

  • There are libraries that don't support a context parameter (yet). When such a library is producing logs, one might want to annotate the log message with the context in which the library call has been made. But without the possibility to "tunnel" a context through the library to the log callback, that is not possible.
  • Debug logs or tracing appear anywhere in your code. Annotating these logs with the current context would require to add a context parameter to every single function, which is simply unnecessary boilerplate and in most cases not feasible.

gctx allows to solve these situations by attaching a context to the current goroutine, which can be retrieved from any code, that is running in the same goroutine or a child, with a simple gctx.Get() call. It is effectively a goroutine-local storage, and could also be used to identify a goroutine, which is not recommended. Therefore it should only be used, if there is really no other possibility available.

How

In fact, goroutines do have an ID, but it is hidden and not accessible with any exported API. That's why there have been a couple of attempts to circumvent that, by implementing a goroutine-local storage in order to solve above problems, like for example:

But all these packages are pretty inefficient and/or are highly dependent on implementation details.

Fortunately, there is another goroutine property that is - at least partly - exported: profiling labels. They can be stored with runtime/pprof.SetGoroutineLabels() in the goroutine-local storage and are automatically inherited by child goroutines. But again, there is no exported API that allows to read these label from the goroutine local storage.

So, in order to make (ab)use of these labels for a gctx, it is required to do one little runtime hack: the internal functions setProfLabel() and getProfLabel() must be made accessible with a //go:linkname instruction. (Of course this is also an implementation detail, that could break anytime. But since it is part of an exported functionality, it can't just disappear, and a fix should always be easily possible.)

These functions are managing a pointer to a labelMap (which is a map[string]string) in the goroutine-local storage. gctx extends this type to piggyback a context.Context on it:

struct {
	labelMap
	context.Context
}

It stores pointers to objects of this extended yet compatible type instead of the original labelMap. A special label in the labelMap is set that indicates, that a context is available. That way the attached context is - like the labels - automatically memory managed and inherited by child goroutines.

Usage

See documentation and examples.

Documentation

Overview

Package gctx implements a goroutine context (gctx). A gctx is a non-global context that is linked to one specific goroutine and can be retrieved from any scope within that goroutine.

Example (Logger)
package main

import (
	"context"
	"fmt"
	"sync"

	"github.com/ansiwen/gctx"
)

type requestID struct{}

// "global" logger instance
func log(s string) {
	ctx := gctx.Get()
	reqID, _ := ctx.Value(requestID{}).(int)
	fmt.Printf("[LOG] reqID: %d - %s\n", reqID, s)
}

// placeholder for an external library function without context support
// doing something and using a global logger
func someLibraryFunction() {
	/* ... */
	log("did something")
	/* ... */
}

func main() {
	var wg sync.WaitGroup
	// asynchronously call a library function for 5 times
	for i := 0; i < 5; i++ {
		wg.Add(1)
		ctx := context.WithValue(context.Background(), requestID{}, i)
		go func() {
			gctx.Set(ctx)
			someLibraryFunction()
			wg.Done()
		}()
	}
	wg.Wait()

}
Output:

[LOG] reqID: 0 - did something
[LOG] reqID: 1 - did something
[LOG] reqID: 2 - did something
[LOG] reqID: 3 - did something
[LOG] reqID: 4 - did something

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Get

func Get() context.Context

Get the current goroutine's gctx, which has been set in the same or a parent goroutine with gctx.Set() before.

func Set

func Set(ctx context.Context)

Set ctx as the current goroutine's gctx. Goroutines created within this goroutine inherit the gctx.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL