GithubHelp home page GithubHelp logo

timandy / routine Goto Github PK

View Code? Open in Web Editor NEW
205.0 6.0 23.0 259 KB

ThreadLocal for Golang.

License: Apache License 2.0

Go 97.73% Assembly 2.05% C 0.22%
go golang goid goroutine localstorage threadlocal fast-threadlocal tls gls

routine's Introduction

routine

Build Status Codecov Go Report Card Documentation Release License

中文版

routine encapsulates and provides some easy-to-use, non-competitive, high-performance goroutine context access interfaces, which can help you access coroutine context information more gracefully.

Introduce

From the very beginning of its design, the Golang language has spared no effort to shield the concept of coroutine context from developers, including the acquisition of coroutine goid, the state of coroutine within the process, and the storage of coroutine context.

If you have used other languages such as C++, Java and so on, then you must be familiar with ThreadLocal, but after starting to use Golang, you will be deeply confused and distressed by the lack of convenient functions like ThreadLocal.

Of course, you can choose to use Context, which carries all the context information, appears in the first input parameter of all functions, and then shuttles around your system.

And the core goal of routine is to open up another way: Introduce goroutine local storage to the Golang world.

Usage & Demo

This chapter briefly introduces how to install and use the routine library.

Install

go get github.com/timandy/routine

Use goid

The following code simply demonstrates the use of routine.Goid():

package main

import (
	"fmt"
	"time"

	"github.com/timandy/routine"
)

func main() {
	goid := routine.Goid()
	fmt.Printf("cur goid: %v\n", goid)
	go func() {
		goid := routine.Goid()
		fmt.Printf("sub goid: %v\n", goid)
	}()

	// Wait for the sub-coroutine to finish executing.
	time.Sleep(time.Second)
}

In this example, the main function starts a new coroutine, so Goid() returns the main coroutine 1 and the child coroutine 6:

cur goid: 1
sub goid: 6

Use ThreadLocal

The following code briefly demonstrates ThreadLocal's creation, setting, getting, spreading across coroutines, etc.:

package main

import (
	"fmt"
	"time"

	"github.com/timandy/routine"
)

var threadLocal = routine.NewThreadLocal[string]()
var inheritableThreadLocal = routine.NewInheritableThreadLocal[string]()

func main() {
	threadLocal.Set("hello world")
	inheritableThreadLocal.Set("Hello world2")
	fmt.Println("threadLocal:", threadLocal.Get())
	fmt.Println("inheritableThreadLocal:", inheritableThreadLocal.Get())

	// The child coroutine cannot read the previously assigned "hello world".
	go func() {
		fmt.Println("threadLocal in goroutine:", threadLocal.Get())
		fmt.Println("inheritableThreadLocal in goroutine:", inheritableThreadLocal.Get())
	}()

	// However, a new sub-coroutine can be started via the Go/GoWait/GoWaitResult function, and all inheritable variables of the current coroutine can be passed automatically.
	routine.Go(func() {
		fmt.Println("threadLocal in goroutine by Go:", threadLocal.Get())
		fmt.Println("inheritableThreadLocal in goroutine by Go:", inheritableThreadLocal.Get())
	})

	// You can also create a task via the WrapTask/WrapWaitTask/WrapWaitResultTask function, and all inheritable variables of the current coroutine can be automatically captured.
	task := routine.WrapTask(func() {
		fmt.Println("threadLocal in task by WrapTask:", threadLocal.Get())
		fmt.Println("inheritableThreadLocal in task by WrapTask:", inheritableThreadLocal.Get())
	})
	go task.Run()

	// Wait for the sub-coroutine to finish executing.
	time.Sleep(time.Second)
}

The execution result is:

threadLocal: hello world
inheritableThreadLocal: Hello world2
threadLocal in goroutine:
inheritableThreadLocal in goroutine:
threadLocal in goroutine by Go:
inheritableThreadLocal in goroutine by Go: Hello world2
threadLocal in task by WrapTask:
inheritableThreadLocal in task by WrapTask: Hello world2

API

This chapter introduces in detail all the interfaces encapsulated by the routine library, as well as their core functions and implementation methods.

Goid() int64

Get the goid of the current goroutine.

It can be obtained directly through assembly code under 386, amd64, armv6, armv7, arm64, loong64, mips, mipsle, mips64, mips64le, ppc64, ppc64le, riscv64, s390x, wasm architectures. This operation has extremely high performance and the time-consuming is usually only one-fifth of rand.Int().

NewThreadLocal[T any]() ThreadLocal[T]

Create a new ThreadLocal[T] instance with the initial value stored with the default value of type T.

NewThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T]

Create a new ThreadLocal[T] instance with the initial value stored as the return value of the method supplier().

NewInheritableThreadLocal[T any]() ThreadLocal[T]

Create a new ThreadLocal[T] instance with the initial value stored with the default value of type T. When a new coroutine is started via Go(), GoWait() or GoWaitResult(), the value of the current coroutine is copied to the new coroutine. When a new task is created via WrapTask(), WrapWaitTask() or WrapWaitResultTask(), the value of the current coroutine is captured to the new task.

NewInheritableThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T]

Create a new ThreadLocal[T] instance with the initial value stored as the return value of the method supplier(). When a new coroutine is started via Go(), GoWait() or GoWaitResult(), the value of the current coroutine is copied to the new coroutine. When a new task is created via WrapTask(), WrapWaitTask() or WrapWaitResultTask(), the value of the current coroutine is captured to the new task.

WrapTask(fun Runnable) FutureTask[any]

Create a new task and capture the inheritableThreadLocals from the current goroutine. This function returns a FutureTask instance, but the return task will not run automatically. You can run it in a sub-goroutine or goroutine-pool by FutureTask.Run() method, wait by FutureTask.Get() or FutureTask.GetWithTimeout() method. When the returned task run panic will be caught and error stack will be printed, the panic will be trigger again when calling FutureTask.Get() or FutureTask.GetWithTimeout() method.

WrapWaitTask(fun CancelRunnable) FutureTask[any]

Create a new task and capture the inheritableThreadLocals from the current goroutine. This function returns a FutureTask instance, but the return task will not run automatically. You can run it in a sub-goroutine or goroutine-pool by FutureTask.Run() method, wait by FutureTask.Get() or FutureTask.GetWithTimeout() method. When the returned task run panic will be caught, the panic will be trigger again when calling FutureTask.Get() or FutureTask.GetWithTimeout() method.

WrapWaitResultTask[TResult any](fun CancelCallable[TResult]) FutureTask[TResult]

Create a new task and capture the inheritableThreadLocals from the current goroutine. This function returns a FutureTask instance, but the return task will not run automatically. You can run it in a sub-goroutine or goroutine-pool by FutureTask.Run() method, wait and get result by FutureTask.Get() or FutureTask.GetWithTimeout() method. When the returned task run panic will be caught, the panic will be trigger again when calling FutureTask.Get() or FutureTask.GetWithTimeout() method.

Go(fun Runnable)

Start a new coroutine and automatically copy all contextual inheritableThreadLocals data of the current coroutine to the new coroutine. Any panic while the child coroutine is executing will be caught and the stack automatically printed.

GoWait(fun CancelRunnable) FutureTask[any]

Start a new coroutine and automatically copy all contextual inheritableThreadLocals data of the current coroutine to the new coroutine. You can wait for the sub-coroutine to finish executing through the FutureTask.Get() or FutureTask.GetWithTimeout() method that returns a value. Any panic while the child coroutine is executing will be caught and thrown again when FutureTask.Get() or FutureTask.GetWithTimeout() is called.

GoWaitResult[TResult any](fun CancelCallable[TResult]) FutureTask[TResult]

Start a new coroutine and automatically copy all contextual inheritableThreadLocals data of the current coroutine to the new coroutine. You can wait for the sub-coroutine to finish executing and get the return value through the FutureTask.Get() or FutureTask.GetWithTimeout() method of the return value. Any panic while the child coroutine is executing will be caught and thrown again when FutureTask.Get() or FutureTask.GetWithTimeout() is called.

More API Documentation

Garbage Collection

routine allocates a thread structure for each coroutine, which stores context variable information related to the coroutine.

A pointer to this structure is stored on the g.labels field of the coroutine structure.

When the coroutine finishes executing and exits, g.labels will be set to nil, no longer referencing the thread structure.

The thread structure will be collected at the next GC.

If the data stored in thread is not additionally referenced, these data will be collected together.

Support Grid

darwin linux windows freebsd js
386 386
amd64 amd64
armv6 armv6
armv7 armv7
arm64 arm64
loong64 loong64
mips mips
mipsle mipsle
mips64 mips64
mips64le mips64le
ppc64 ppc64
ppc64le ppc64le
riscv64 riscv64
s390x s390x
wasm wasm
darwin linux windows freebsd js

✅: Supported

Thanks

Thanks to all contributors for their contributions!

License

routine is released under the Apache License 2.0.

Copyright 2021-2024 TimAndy

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

routine's People

Contributors

bugaolengdeyuxiaoer avatar dependabot[bot] avatar liangmanlin avatar sisyphsu avatar timandy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

routine's Issues

How to use the api ?

Is there an existing issue for this?

  • I have searched the existing issues

Question

Do we need to call the remove function on the thread local ?

I dont want to loose created variable in memory .

Thanks.

Code of Conduct

  • I agree to follow this project's Code of Conduct

threadlocalmap用切片表示不能复用

Is there an existing issue for this?

  • I have searched the existing issues

Question

threadlocalmap不用id池吗,这样remove以后id一直递增了

Code of Conduct

  • I agree to follow this project's Code of Conduct

[build err] on go1.20

Is there an existing issue for this?

  • I have searched the existing issues

Question

go1.20版本编译异常:github.com/timandy/routine/g.getg0: relocation target type.runtime.g not defined

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Bug] Library is not working in go 1.21

Is there an existing issue for this?

  • I have searched the existing issues

Does this issue reproduce with the latest release?

  • I have upgrade to the latest version

Steps To Reproduce

Library is not working as expected on 1.21.
Tests from this library are failing:

go test ./...
*** GOOS: darwin ***
*** GOARCH: arm64 ***
#numField: 50
#offsetGoid: 152
#offsetPaniconfault: 181
#offsetGopc: 280
#offsetLabels: 344
--- FAIL: TestObject (0.00s)
thread_local_map_test.go:13:
Error Trace:	./thread_local_map_test.go:13
Error:      	Expected and actual point to the same object: 0x10503ef00 &routine.object{}
Test:       	TestObject
FAIL
FAIL	github.com/timandy/routine	3.298s
ok  	github.com/timandy/routine/g	(cached)
FAIL 

Also output of tests on Linux, AMD64:

go test ./...


--- FAIL: TestFutureTask_Routine_Complete (0.87s)
panic: RuntimeError: Task execution timeout after 100ms.
   at github.com/timandy/routine.(*futureTask).timeout() in /home/dovechkin/workspace/routine/future_task.go:116
   at github.com/timandy/routine.(*futureTask).GetWithTimeout() in /home/dovechkin/workspace/routine/future_task.go:92
   at github.com/timandy/routine.TestFutureTask_Routine_Complete() in /home/dovechkin/workspace/routine/future_task_test.go:497
   at testing.tRunner() in /usr/local/go/src/testing/testing.go:1595
   --- End of error stack trace ---
   created by testing.(*T).Run() in /usr/local/go/src/testing/testing.go:1648 [recovered]
        panic: RuntimeError: Task execution timeout after 100ms.
   at github.com/timandy/routine.(*futureTask).timeout() in /home/dovechkin/workspace/routine/future_task.go:116
   at github.com/timandy/routine.(*futureTask).GetWithTimeout() in /home/dovechkin/workspace/routine/future_task.go:92
   at github.com/timandy/routine.TestFutureTask_Routine_Complete() in /home/dovechkin/workspace/routine/future_task_test.go:497
   at testing.tRunner() in /usr/local/go/src/testing/testing.go:1595
   --- End of error stack trace ---
   created by testing.(*T).Run() in /usr/local/go/src/testing/testing.go:1648

goroutine 4349 [running]:
testing.tRunner.func1.2({0x5e8b40, 0xc000100190})
        /usr/local/go/src/testing/testing.go:1545 +0x238
testing.tRunner.func1()
        /usr/local/go/src/testing/testing.go:1548 +0x397
panic({0x5e8b40?, 0xc000100190?})
        /usr/local/go/src/runtime/panic.go:914 +0x21f
github.com/timandy/routine.(*futureTask).GetWithTimeout(0xc000a26200, 0x4d5000?)
        /home/dovechkin/workspace/routine/future_task.go:97 +0x1f5
github.com/timandy/routine.TestFutureTask_Routine_Complete(0xc000a0eb60?)
        /home/dovechkin/workspace/routine/future_task_test.go:497 +0x84
testing.tRunner(0xc000a0ed00, 0x635ab8)
        /usr/local/go/src/testing/testing.go:1595 +0xff
created by testing.(*T).Run in goroutine 1
        /usr/local/go/src/testing/testing.go:1648 +0x3ad
FAIL    github.com/timandy/routine      25.452s
ok      github.com/timandy/routine/g    1.737s
FAIL

Expected Behavior

Library tests should pass

Current Behavior

Library tests are failing

Environment

go 1.21

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Question] invalid operation: cannot index routine.NewThreadLocal (value of type func() routine.ThreadLocal)

Is there an existing issue for this?

  • I have searched the existing issues

Question

The example "Use ThreadLocal" does not work, it reports "invalid operation: cannot index routine.NewThreadLocal (value of type func() routine.ThreadLocal)", "invalid operation: cannot index routine.NewInheritableThreadLocal (value of type func() routine.ThreadLocal)
"

golang version 1.21

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Question] can I directly modify the golang runtime?

Is there an existing issue for this?

  • I have searched the existing issues

Question

Excuse me, can I directly modify the golang runtime?
Because this information is available in the runtime, such as goid or more information about GMP

Code of Conduct

  • I agree to follow this project's Code of Conduct

如何验证thread是否被GC回收?占用的内存是否被释放?

Is there an existing issue for this?

  • I have searched the existing issues

Question

var (
	threadLocal1 = routine.NewThreadLocal[string]()
)

func main() {
	var m1 runtime.MemStats
	runtime.ReadMemStats(&m1)
	fmt.Printf("1 - %d Kb\n", m1.Alloc/1024)
        for i := 0; i < 1000; i++ {
          go func(){
             threadLocal1.Set("bigFile")
             time.Sleep(time.Second)

             threadLocal1.Get()
	     time.Sleep(time.Second)
	     
             var sub runtime.MemStats
	     runtime.ReadMemStats(&sub)
	     fmt.Printf("sub - %d Kb\n", sub.Alloc/1024)
           }
        }
        
        time.Sleep(time.Second * 5)

	var m2 runtime.MemStats
	runtime.ReadMemStats(&m2)
	fmt.Printf("2 - %d Kb\n", m2.Alloc/1024)
}

输出打印的内容使用量并没有下来。

Code of Conduct

  • I agree to follow this project's Code of Conduct

TestPProf会报错

func TestPProf(t *testing.T) {
	const concurrency = 100
	const loopTimes = 10000
	tls := NewThreadLocal()
	tls.Set("你好")
	wg := &sync.WaitGroup{}
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		tmp := i
		go func() {
			for j := 0; j < loopTimes; j++ {
				time.Sleep(10 * time.Millisecond)
				tls.Set(tmp)
				assert.Equal(t, tmp, tls.Get())
				pprof.Do(context.Background(), pprof.Labels("key", "value"), func(ctx context.Context) {
					tls.Set("hi")
					label, find := pprof.Label(ctx, "key")
					assert.True(t, find)
					assert.Equal(t, "value", label)
					//
					//assert.Nil(t, currentThread(false))
					//assert.Nil(t, tls.Get())
					//tls.Set("hi")
					assert.Equal(t, "hi", tls.Get())
					//
					label2, find2 := pprof.Label(ctx, "key")
					assert.True(t, find2)
					assert.Equal(t, "value", label2)
				})
				assert.Nil(t, tls.Get())
			}
			wg.Done()
		}()
	}
	assert.Nil(t, pprof.StartCPUProfile(&bytes.Buffer{}))
	wg.Wait()
	pprof.StopCPUProfile()
	assert.Equal(t, "你好", tls.Get())
}

使用上述测试代码,发现偶发如下报错

[root@ routine]# go test -run TestPProf
unexpected fault address 0x5f87cb
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x2 addr=0x5f87cb pc=0x598424]

goroutine 27 [running]:
runtime.throw({0x5f8c17?, 0xc00017f828?})
        /usr/local/go/src/runtime/panic.go:992 +0x71 fp=0xc000328d58 sp=0xc000328d28 pc=0x436dd1
runtime.sigpanic()
        /usr/local/go/src/runtime/signal_unix.go:825 +0x305 fp=0xc000328da8 sp=0xc000328d58 pc=0x44cdc5
github.com/timandy/routine.(*threadLocalMap).set(...)
        /mnt/hgfs/go/routine/thread_local_map.go:24
github.com/timandy/routine.(*threadLocal).Set(0xc00005d400, {0x5bf720?, 0x6400b0})
        /mnt/hgfs/go/routine/thread_local.go:36 +0x64 fp=0xc000328de0 sp=0xc000328da8 pc=0x598424
github.com/timandy/routine.TestPProf.func1.1({0x641c40, 0xc000351ce0})
        /mnt/hgfs/go/routine/thread_test.go:34 +0x69 fp=0xc000328e70 sp=0xc000328de0 pc=0x5a98e9
runtime/pprof.Do({0x641bd0?, 0xc0000160c0?}, {{0xc00032ea40?, 0x742a98?, 0x5bee20?}}, 0xc000328fb0)
        /usr/local/go/src/runtime/pprof/runtime.go:40 +0xa3 fp=0xc000328ee0 sp=0xc000328e70 pc=0x5092e3
github.com/timandy/routine.TestPProf.func1()
        /mnt/hgfs/go/routine/thread_test.go:33 +0xe7 fp=0xc000328fe0 sp=0xc000328ee0 pc=0x5a95a7
runtime.goexit()
        /usr/local/go/src/runtime/asm_amd64.s:1571 +0x1 fp=0xc000328fe8 sp=0xc000328fe0 pc=0x4681e1
created by github.com/timandy/routine.TestPProf
        /mnt/hgfs/go/routine/thread_test.go:28 +0x8e

在windows平台不会发生

go1.18.1

目前可以猜测的是,thread经过gc后,是没有重置数据的

我提了一个pr,你可以看看这样处理是否可以解决

代码段求解

routine/thread.go

Lines 65 to 69 in 118c005

old := gp.setPanicOnFault(true)
defer func() {
gp.setPanicOnFault(old)
recover()
}()

这一段代码的作用是什么,不太理解,希望可以解答一下

API 是否应该命名为 GoLocal 或者 RoutineLocal 而非 ThreadLocal ?

Is there an existing issue for this?

  • I have searched the existing issues

Question

Go 里面并没有直接管控 “Thread” 的能力,都是 GoRoutine,所以,这个 “Local” 其实是对 GoRoutine 的。
虽然 “ThreadLocal” 能很好对接从 Java 过来的开发人员,但实际上这个命名并不是很妥当。

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Question] 也许用 cgoCtxt 这个字段(而不是 lables) 会更安全?

Is there an existing issue for this?

  • I have searched the existing issues

Question

cgoCtxt 只在 cgo 调用时记录上下文地址, 且 cgo 调用后, cgoCtxt 数组会自动清空
相比 labels 字段, cgoCtxt 的使用频率更低, 发生冲突的概率也会更低

下面这段代码是我借用 g.cgoCtxt 来存储 GoroutineKiller 来做协程的超时退出:

type GoroutineKiller struct {
	deadlineStackSize uintptr
	deadlineTime      time.Time
	done              bool
}

func SetKiller(timeout time.Duration, maxStack uintptr) (deferdo func(fn func(userPanicked any, killed Killed))) {
	var killer = new(GoroutineKiller)
	stack_hi, sp, cgoCtxt := runtime.GetG()
	if cgoCtxt.Len != 0 { // 当前协程 cgoCtxt 已被使用

	} else {
		cgoCtxt.Data = unsafe.Pointer(killer)
		killer.deadlineStackSize = maxStack + (stack_hi - sp /*已占用栈大小*/)
		killer.deadlineTime = time.Now().Add(timeout)
	}
	return func(fn func(panicked any, killed Killed)) {
		_, _, cgoCtxt := runtime.GetG()
		if cgoCtxt.Len == 0 {
			cgoCtxt.Data = nil // goexit0 不会清理这个字段, 得主动清理
		}
		var panicked any
		var killed_ Killed
		if e := recover(); e != nil {
			if k, ok := e.(Killed); ok {
				killed_ = k
			} else {
				panicked = e
			}
		}
		fn(panicked, killed_)
	}
}

var time_now time.Time
var checker_count int
var checker_close = true

func init() {
	time_now = time.Now()
	go func() {
		for range time.NewTicker(1 * time.Microsecond).C {
			time_now = time.Now()
			if checker_count == 100-1 { // 抽检 1%
				checker_count++
				checker_close = false
			} else if checker_count == 100 {
				checker_count = 0
				checker_close = true
			} else {
				checker_count++
			}
		}
	}()
}

type Killed int

const KilledTimeout Killed = 1234
const KilledStackTooLarge Killed = 5678

func CheckKiller() {
	if checker_close {
		return
	}
	stack_hi, sp, cgoCtxt := runtime.GetG()
	if cgoCtxt.Data == nil || cgoCtxt.Len != 0 {
		return
	}
	var killer = (*GoroutineKiller)(cgoCtxt.Data)
	if killer.done {
		return
	}
	if time_now.After(killer.deadlineTime) {
		killer.done = true
		panic(KilledTimeout)
	}
	if size := stack_hi - sp; size > killer.deadlineStackSize {
		killer.done = true
		panic(KilledStackTooLarge)
	}
}

Code of Conduct

  • I agree to follow this project's Code of Conduct

When will version 1.22.5 be supported?

Is there an existing issue for this?

  • I have searched the existing issues

Question

When will version 1.22.5 be supported?

Code of Conduct

  • I agree to follow this project's Code of Conduct

[Question] How does routine use g.labels to store data?

Is there an existing issue for this?

  • I have searched the existing issues

Question

thread 结构体的数据存在 g.labels 里

  1. golang 的设计中,哪些golang自带的基础组件会占用到 g.labels 成员?
  2. 怎么检测出 g.labels 成员被哪个模块占用?

Code of Conduct

  • I agree to follow this project's Code of Conduct

如何使用泛型版本(How to use the generic version)

如何使用泛型版本

  1. 编辑你项目的 go.mod 文件,把 go 版本修改为 1.18
  2. 复制 feature/generic 分支最新提交的 SHA 值。
  3. 到你项目下执行 go get github.com/timandy/routine@SHA,替换 SHA 为复制的值。

How to use the generic version

  1. Edit your the project's go.mod file and change the go version to 1.18.
  2. Copy the SHA value of the latest commit of the feature/generic branch.
  3. Go to your project and execute go get github.com/timandy/routine@SHA, replace SHA with the copied value.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.