pax_global_header00006660000000000000000000000064151723517120014516gustar00rootroot0000000000000052 comment=e4d7cc010d14a50954dee8807abe3cb981dd7e0f golang-github-timandy-routine-1.1.6/000077500000000000000000000000001517235171200174005ustar00rootroot00000000000000golang-github-timandy-routine-1.1.6/.gitattributes000066400000000000000000000011051517235171200222700ustar00rootroot00000000000000# Handle line endings automatically for files detected as text # and leave all files detected as binary untouched. * text=auto # # The above will handle all files NOT found below # # These files are text and should be normalized (Convert crlf => lf) *.c text *.cpp text *.go text *.h text *.md text *.mod text *.s text *.sum text # These files are binary and should be left untouched # (binary is a macro for -text -diff) *.dll binary *.exe binary *.so binary golang-github-timandy-routine-1.1.6/.github/000077500000000000000000000000001517235171200207405ustar00rootroot00000000000000golang-github-timandy-routine-1.1.6/.github/ISSUE_TEMPLATE/000077500000000000000000000000001517235171200231235ustar00rootroot00000000000000golang-github-timandy-routine-1.1.6/.github/ISSUE_TEMPLATE/bug.yml000066400000000000000000000041721517235171200244270ustar00rootroot00000000000000name: Bugs description: Create a report to help us improve title: "[Bug] " labels: [ "bug" ] body: - type: markdown attributes: value: Thanks for taking the time to fill out this bug report! - type: checkboxes attributes: label: Is there an existing issue for this? description: Please search to see if an issue already exists for the bug you encountered. options: - label: I have searched the existing issues required: true - type: checkboxes attributes: label: Does this issue reproduce with the latest release? description: Please upgrade to the latest version to see if the issue still exists. options: - label: I have upgrade to the latest version required: true - type: textarea attributes: label: Steps To Reproduce description: The smallest possible code example to show the problem that can be compiled. placeholder: | ```go package main import ( "fmt" ) func main() { fmt.Println("Hello World") } ``` validations: required: true - type: textarea attributes: label: Expected Behavior description: A concise description of what you expected to happen. placeholder: | The console prints `Hello World` validations: required: true - type: textarea attributes: label: Current Behavior description: A concise description of what you're experiencing. placeholder: | The console prints nothing validations: required: true - type: textarea attributes: label: Environment description: What version of Go are you using (`go version`)? placeholder: | `go version go1.18.3 windows/amd64` validations: required: true - type: checkboxes id: terms attributes: label: Code of Conduct description: By submitting this issue, you agree to follow our [Code of Conduct](../blob/main/CODE_OF_CONDUCT.md) options: - label: I agree to follow this project's Code of Conduct required: true ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/ISSUE_TEMPLATE/config.yml�������������������������������0000664�0000000�0000000�00000000034�15172351712�0025110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blank_issues_enabled: false ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/ISSUE_TEMPLATE/question.yml�����������������������������0000664�0000000�0000000�00000001655�15172351712�0025524�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������name: Questions description: Create an issue for help title: "[Question] <title>" labels: [ "question" ] body: - type: markdown attributes: value: Thanks for taking the time to fill out this issue! - type: checkboxes attributes: label: Is there an existing issue for this? description: Please search to see if an issue already exists for the issue you encountered. options: - label: I have searched the existing issues required: true - type: textarea attributes: label: Question description: the question you want to ask. validations: required: true - type: checkboxes id: terms attributes: label: Code of Conduct description: By submitting this issue, you agree to follow our [Code of Conduct](../blob/main/CODE_OF_CONDUCT.md) options: - label: I agree to follow this project's Code of Conduct required: true �����������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/PULL_REQUEST_TEMPLATE.md��������������������������������0000664�0000000�0000000�00000000531�15172351712�0024540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- With pull requests: - Open your pull request against `main` branch. - It should pass all tests in the available continuous integration systems such as GitHub Actions. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README.md and README_zh.md. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/dependabot.yml������������������������������������������0000664�0000000�0000000�00000000663�15172351712�0023575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Configuration file of GitHub Dependabot version: 2 updates: # Maintain dependencies for gomod - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" time: "08:00" timezone: "Asia/Shanghai" # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" time: "08:00" timezone: "Asia/Shanghai" �����������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/workflows/����������������������������������������������0000775�0000000�0000000�00000000000�15172351712�0022775�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/workflows/build.yml�������������������������������������0000664�0000000�0000000�00000026677�15172351712�0024641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Workflow file of GitHub Actions name: build on: push: branches: - main - feature/** pull_request: branches: - main jobs: Lint: runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Lint uses: golangci/golangci-lint-action@v8 CodeQL: needs: Lint runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 Test: needs: Lint runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: os: [ darwin, linux, windows, freebsd, js ] arch: [ 386, amd64, armv6, armv7, arm64, loong64, mips, mipsle, mips64, mips64le, ppc64, ppc64le, riscv64, s390x, wasm ] go: [ '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24', '1.25' ] exclude: # darwin excludes - os: darwin arch: 386 - os: darwin arch: armv6 - os: darwin arch: armv7 - os: darwin arch: loong64 - os: darwin arch: mips - os: darwin arch: mipsle - os: darwin arch: mips64 - os: darwin arch: mips64le - os: darwin arch: ppc64 - os: darwin arch: ppc64le - os: darwin arch: riscv64 - os: darwin arch: s390x - os: darwin arch: wasm # linux excludes - os: linux arch: loong64 go: 1.18 - os: linux arch: mips64 go: 1.22 - os: linux arch: mips64le go: 1.22 - os: linux arch: wasm # windows excludes - os: windows arch: armv6 - os: windows arch: armv7 - os: windows arch: arm64 - os: windows arch: loong64 - os: windows arch: mips - os: windows arch: mipsle - os: windows arch: mips64 - os: windows arch: mips64le - os: windows arch: ppc64 - os: windows arch: ppc64le - os: windows arch: riscv64 - os: windows arch: s390x - os: windows arch: wasm # freebsd excludes - os: freebsd arch: armv6 - os: freebsd arch: armv7 - os: freebsd arch: arm64 - os: freebsd arch: loong64 - os: freebsd arch: mips - os: freebsd arch: mipsle - os: freebsd arch: mips64 - os: freebsd arch: mips64le - os: freebsd arch: ppc64 - os: freebsd arch: ppc64le - os: freebsd arch: riscv64 - os: freebsd arch: s390x - os: freebsd arch: wasm # js excludes - os: js arch: 386 - os: js arch: amd64 - os: js arch: armv6 - os: js arch: armv7 - os: js arch: arm64 - os: js arch: loong64 - os: js arch: mips - os: js arch: mipsle - os: js arch: mips64 - os: js arch: mips64le - os: js arch: ppc64 - os: js arch: ppc64le - os: js arch: riscv64 - os: js arch: s390x include: # combine runs on - os: darwin runs-on: macos-13 - os: darwin arch: arm64 runs-on: macos-latest - os: linux runs-on: ubuntu-latest - os: windows runs-on: windows-latest - os: freebsd runs-on: ubuntu-latest - os: js runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} cache: false # darwin - name: 'Test on [darwin] arch [amd64]' if: ${{ matrix.os == 'darwin' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Test on [darwin] arch [arm64]' if: ${{ matrix.os == 'darwin' && contains(fromJson('["arm64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic ./... # linux - name: 'Test on [linux] arch [386]' if: ${{ matrix.os == 'linux' && contains(fromJson('["386"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Test on [linux] arch [amd64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Setup qemu-user-static on [linux] arch [armv6, armv7, arm64, mips, mipsle, mips64, mips64le, ppc64, ppc64le, riscv64, s390x]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv6", "armv7", "arm64", "mips", "mipsle", "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x"]'), matrix.arch) }} run: | sudo apt-get update sudo apt-get -y install qemu-user-static - name: 'Test on [linux] arch [armv6]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv6"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: arm GOARM: 6 run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Test on [linux] arch [armv7]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv7"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: arm GOARM: 7 run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Test on [linux] arch [mips, mipsle]' if: ${{ matrix.os == 'linux' && contains(fromJson('["mips", "mipsle"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} GOMIPS: softfloat run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Test on [linux] arch [arm64, mips64, mips64le, ppc64, ppc64le, riscv64, s390x]' if: ${{ matrix.os == 'linux' && contains(fromJson('["arm64", "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Setup qemu-loongarch64-static on [linux] arch [loong64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["loong64"]'), matrix.arch) }} run: | sudo wget -O /usr/bin/qemu-loongarch64-static https://github.com/loongson/build-tools/releases/download/2025.02.21/qemu-loongarch64 sudo chmod +x /usr/bin/qemu-loongarch64-static sudo mkdir -p /usr/libexec/qemu-binfmt sudo ln -s /usr/bin/qemu-loongarch64-static /usr/libexec/qemu-binfmt/loongarch64-binfmt-P sudo sh -c 'echo ":qemu-loongarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01:\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/libexec/qemu-binfmt/loongarch64-binfmt-P:" > /proc/sys/fs/binfmt_misc/register' - name: 'Test on [linux] arch [loong64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["loong64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... # windows - name: 'Test on [windows] arch [386]' if: ${{ matrix.os == 'windows' && contains(fromJson('["386"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: 'Downgrade gcc for go1.18 on [windows] arch [amd64]' if: ${{ matrix.os == 'windows' && contains(fromJson('["amd64"]'), matrix.arch) && matrix.go == '1.18' }} run: | Invoke-WebRequest -Uri 'https://jaist.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-win32/sjlj/x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0.7z?viasf=1' -OutFile gcc8.7z Remove-Item 'C:\mingw64' -Recurse -Force 7z x gcc8.7z -oC:\ -y gcc --version - name: 'Test on [windows] arch [amd64]' if: ${{ matrix.os == 'windows' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic ./... # freebsd - name: 'Build for [freebsd] arch [386, amd64]' if: ${{ matrix.os == 'freebsd' && contains(fromJson('["386", "amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -c -covermode=atomic - name: 'Test on [freebsd] arch [386, amd64]' if: ${{ matrix.os == 'freebsd' && contains(fromJson('["386", "amd64"]'), matrix.arch) }} uses: vmactions/freebsd-vm@v1 with: run: ./routine.test -test.v -test.coverprofile='coverage.txt' # js - name: 'Setup Node.js on [js] arch [wasm]' if: ${{ matrix.os == 'js' && contains(fromJson('["wasm"]'), matrix.arch) }} uses: actions/setup-node@v4 with: node-version: 18 - name: 'Test on [js] arch [wasm]' if: ${{ matrix.os == 'js' && contains(fromJson('["wasm"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: | go_version=$(go env GOVERSION | cut -c3-) max_version=$(printf '%s\n' "$go_version" '1.24' | sort -V | tail -n1) if [ "$go_version" = "$max_version" ]; then PATH="$PATH:$(go env GOROOT)/lib/wasm" else PATH="$PATH:$(go env GOROOT)/misc/wasm" fi go test -v -coverprofile='coverage.txt' -covermode=atomic ./... - name: Codecov uses: codecov/codecov-action@v5 with: name: Codecov on ${{ matrix.os }}/${{ matrix.arch }} go${{ matrix.go }} token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false �����������������������������������������������������������������golang-github-timandy-routine-1.1.6/.github/workflows/static.yml������������������������������������0000664�0000000�0000000�00000027614�15172351712�0025021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Workflow file of GitHub Actions name: static on: push: branches: - main - feature/** pull_request: branches: - main jobs: Lint: runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Lint uses: golangci/golangci-lint-action@v8 CodeQL: needs: Lint runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: go - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 Test: needs: Lint runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: os: [ darwin, linux, windows, freebsd, js ] arch: [ 386, amd64, armv6, armv7, arm64, loong64, mips, mipsle, mips64, mips64le, ppc64, ppc64le, riscv64, s390x, wasm ] go: [ '1.18', '1.19', '1.20', '1.21', '1.22', '1.23', '1.24', '1.25' ] exclude: # darwin excludes - os: darwin arch: 386 - os: darwin arch: armv6 - os: darwin arch: armv7 - os: darwin arch: loong64 - os: darwin arch: mips - os: darwin arch: mipsle - os: darwin arch: mips64 - os: darwin arch: mips64le - os: darwin arch: ppc64 - os: darwin arch: ppc64le - os: darwin arch: riscv64 - os: darwin arch: s390x - os: darwin arch: wasm # linux excludes - os: linux arch: loong64 go: 1.18 - os: linux arch: mips64 go: 1.22 - os: linux arch: mips64le go: 1.22 - os: linux arch: wasm # windows excludes - os: windows arch: armv6 - os: windows arch: armv7 - os: windows arch: arm64 - os: windows arch: loong64 - os: windows arch: mips - os: windows arch: mipsle - os: windows arch: mips64 - os: windows arch: mips64le - os: windows arch: ppc64 - os: windows arch: ppc64le - os: windows arch: riscv64 - os: windows arch: s390x - os: windows arch: wasm # freebsd excludes - os: freebsd arch: armv6 - os: freebsd arch: armv7 - os: freebsd arch: arm64 - os: freebsd arch: loong64 - os: freebsd arch: mips - os: freebsd arch: mipsle - os: freebsd arch: mips64 - os: freebsd arch: mips64le - os: freebsd arch: ppc64 - os: freebsd arch: ppc64le - os: freebsd arch: riscv64 - os: freebsd arch: s390x - os: freebsd arch: wasm # js excludes - os: js arch: 386 - os: js arch: amd64 - os: js arch: armv6 - os: js arch: armv7 - os: js arch: arm64 - os: js arch: loong64 - os: js arch: mips - os: js arch: mipsle - os: js arch: mips64 - os: js arch: mips64le - os: js arch: ppc64 - os: js arch: ppc64le - os: js arch: riscv64 - os: js arch: s390x include: # combine runs on - os: darwin runs-on: macos-13 - os: darwin arch: arm64 runs-on: macos-latest - os: linux runs-on: ubuntu-latest - os: windows runs-on: windows-latest - os: freebsd runs-on: ubuntu-latest - os: js runs-on: ubuntu-latest steps: - name: Checkout scm uses: actions/checkout@v5 - name: Set up Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} cache: false # prepare - name: 'Install routinex' run: go install github.com/timandy/routinex@latest # darwin - name: 'Test on [darwin] arch [amd64]' if: ${{ matrix.os == 'darwin' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Test on [darwin] arch [arm64]' if: ${{ matrix.os == 'darwin' && contains(fromJson('["arm64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... # linux - name: 'Test on [linux] arch [386]' if: ${{ matrix.os == 'linux' && contains(fromJson('["386"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Test on [linux] arch [amd64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Setup qemu-user-static on [linux] arch [armv6, armv7, arm64, mips, mipsle, mips64, mips64le, ppc64, ppc64le, riscv64, s390x]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv6", "armv7", "arm64", "mips", "mipsle", "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x"]'), matrix.arch) }} run: | sudo apt-get update sudo apt-get -y install qemu-user-static - name: 'Test on [linux] arch [armv6]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv6"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: arm GOARM: 6 run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Test on [linux] arch [armv7]' if: ${{ matrix.os == 'linux' && contains(fromJson('["armv7"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: arm GOARM: 7 run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Test on [linux] arch [mips, mipsle]' if: ${{ matrix.os == 'linux' && contains(fromJson('["mips", "mipsle"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} GOMIPS: softfloat run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Test on [linux] arch [arm64, mips64, mips64le, ppc64, ppc64le, riscv64, s390x]' if: ${{ matrix.os == 'linux' && contains(fromJson('["arm64", "mips64", "mips64le", "ppc64", "ppc64le", "riscv64", "s390x"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Setup qemu-loongarch64-static on [linux] arch [loong64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["loong64"]'), matrix.arch) }} run: | sudo wget -O /usr/bin/qemu-loongarch64-static https://github.com/loongson/build-tools/releases/download/2025.02.21/qemu-loongarch64 sudo chmod +x /usr/bin/qemu-loongarch64-static sudo mkdir -p /usr/libexec/qemu-binfmt sudo ln -s /usr/bin/qemu-loongarch64-static /usr/libexec/qemu-binfmt/loongarch64-binfmt-P sudo sh -c 'echo ":qemu-loongarch64:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01:\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/libexec/qemu-binfmt/loongarch64-binfmt-P:" > /proc/sys/fs/binfmt_misc/register' - name: 'Test on [linux] arch [loong64]' if: ${{ matrix.os == 'linux' && contains(fromJson('["loong64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... # windows - name: 'Test on [windows] arch [386]' if: ${{ matrix.os == 'windows' && contains(fromJson('["386"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: 'Downgrade gcc for go1.18 on [windows] arch [amd64]' if: ${{ matrix.os == 'windows' && contains(fromJson('["amd64"]'), matrix.arch) && matrix.go == '1.18' }} run: | Invoke-WebRequest -Uri 'https://jaist.dl.sourceforge.net/project/mingw-w64/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-win32/sjlj/x86_64-8.1.0-release-win32-sjlj-rt_v6-rev0.7z?viasf=1' -OutFile gcc8.7z Remove-Item 'C:\mingw64' -Recurse -Force 7z x gcc8.7z -oC:\ -y gcc --version - name: 'Test on [windows] arch [amd64]' if: ${{ matrix.os == 'windows' && contains(fromJson('["amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -race -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... # freebsd - name: 'Build for [freebsd] arch [386, amd64]' if: ${{ matrix.os == 'freebsd' && contains(fromJson('["386", "amd64"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: go test -v -c -covermode=atomic -a -toolexec='routinex -v' - name: 'Test on [freebsd] arch [386, amd64]' if: ${{ matrix.os == 'freebsd' && contains(fromJson('["386", "amd64"]'), matrix.arch) }} uses: vmactions/freebsd-vm@v1 with: run: ./routine.test -test.v -test.coverprofile='coverage.txt' # js - name: 'Setup Node.js on [js] arch [wasm]' if: ${{ matrix.os == 'js' && contains(fromJson('["wasm"]'), matrix.arch) }} uses: actions/setup-node@v4 with: node-version: 18 - name: 'Test on [js] arch [wasm]' if: ${{ matrix.os == 'js' && contains(fromJson('["wasm"]'), matrix.arch) }} env: GOOS: ${{ matrix.os }} GOARCH: ${{ matrix.arch }} run: | go_version=$(go env GOVERSION | cut -c3-) max_version=$(printf '%s\n' "$go_version" '1.24' | sort -V | tail -n1) if [ "$go_version" = "$max_version" ]; then PATH="$PATH:$(go env GOROOT)/lib/wasm" else PATH="$PATH:$(go env GOROOT)/misc/wasm" fi go test -v -coverprofile='coverage.txt' -covermode=atomic -a -toolexec='routinex -v' ./... - name: Codecov uses: codecov/codecov-action@v5 with: name: Codecov on ${{ matrix.os }}/${{ matrix.arch }} go${{ matrix.go }} token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false ��������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/.gitignore������������������������������������������������������0000664�0000000�0000000�00000000437�15172351712�0021374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # IntelliJ .idea ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/CHANGELOG.md����������������������������������������������������0000664�0000000�0000000�00000022445�15172351712�0021220�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!--变更日志--> # v1.1.6 Release notes ### Features - Support capture and restore goroutine's context by `createInheritedMap()` and `restoreInheritedMap()` methods. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.6](https://github.com/timandy/routine/tree/v1.1.6) --- # v1.1.5 Release notes ### Bugs - Fix missing nil check for the underlying data when copying the context of `InheritableThreadLocal`. ### Features - Support go version range `go1.18` ~ `go1.25`(New support `go1.24` and `go1.25`). - Add a new `static mode`, which improves performance by over `20%` and provides higher memory safety by adding the compilation parameter `-a -toolexec='routinex -v'`. ### Changes - Modify the `goid` type to `uint64`. - Update copyright. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.5](https://github.com/timandy/routine/tree/v1.1.5) --- # v1.1.4 Release notes ### Features - Support go version range `go1.18` ~ `go1.23`(New support `go1.23`). ### Changes - Fix interface conversion error: getting nil value from ThreadLocal[T], where T is interface type. - Update copyright. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.4](https://github.com/timandy/routine/tree/v1.1.4) --- # v1.1.3 Release notes ### Features - Support go version range `go1.18` ~ `go1.21`. - Support `generic` programming. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.3](https://github.com/timandy/routine/tree/v1.1.3) --- # v1.1.2 Release notes ### Features - Support go version range `go1.13` ~ `go1.21`(New support `go1.21`). - Support capture values of `InheritableThreadLocal` by `WrapTask()`, `WrapWaitTask()` and `WrapWaitResultTask()` methods. - Support run `FutureTask` by `FutureTask.Run()` method. - Define function type `Runnable` and `FutureCallable`. ### Changes - Rename type `Future` to `FutureTask`. - Skip first runtime panic stack automatically for `RuntimeError`. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.2](https://github.com/timandy/routine/tree/v1.1.2) --- # v1.1.1 Release notes ### Features - Support go version range `go1.13` ~ `go1.20`(New support `go1.20`). ### Changes - Fix a memory leak risk caused by Timer. - Reduce memory by using less chan. - Update copyright. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.1](https://github.com/timandy/routine/tree/v1.1.1) --- # v1.1.0 Release notes ### Features - Support more arch `loong64`, `mips`, `mipsle`, `mips64`, `mips64le`, `ppc64le`, `riscv64`, `wasm`. ### Changes - Upgrade dependencies to the latest version. - Modify continuous integration script to support go1.19. # Links - Source code [https://github.com/timandy/routine/tree/v1.1.0](https://github.com/timandy/routine/tree/v1.1.0) --- # v1.0.9 Release notes ### Features - Support arch `386` & `amd64` on `freebsd` and arch `ppc64` & `s390x` on `linux`. - Support `Cancel()` and `GetWithTimeout()` methods for type `Future`. - Support checking whether the tasks created by `GoWait(CancelRunnable)` and `GoWaitResult(CancelCallable)` methods are canceled. ### Changes - Fix spell error of type `Future`. - Rename type `Any` to `any`. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.9](https://github.com/timandy/routine/tree/v1.0.9) --- # v1.0.8 Release notes ### Changes - Rename `StackError` to `RuntimeError`. - Support error nesting for `RuntimeError`. - Beautify the error message of `RuntimeError`. - Remove `bytesconv.Bytes()` and `bytesconv.String()` methods. - Restore to the previous value if an overflow occurs when getting the index of `ThreadLocal`. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.8](https://github.com/timandy/routine/tree/v1.0.8) --- # v1.0.7 Release notes ### Bugs - Fix released `thread` struct may be resurrected from invalid memory and cause fault error. ### Changes - Modify the error message format of `StackError`. - Define function type `Supplier` for `threadLocal` and `inheritableThreadLocal` types. - Define function type `Runnable` and `Callable` for `Go(Runnable)`, `GoWait(Runnable)` and `GoWaitResult(Callable)` methods. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.7](https://github.com/timandy/routine/tree/v1.0.7) --- # v1.0.6 Release notes ### Bugs - Fix fault error when pprof is running. ### Features - Support more architectures `386`, `amd64`, `armv6`, `armv7`, `arm64`. ### Changes - Read and write `coroutine` information through the `gohack` library, theoretically support unreleased `go` versions in the future. - When `runtime.g` cannot be obtained natively, `panic` directly instead of falling back to invoke `runtime.Stack()` method. - Remove api `ThreadLocal.Id()`. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.6](https://github.com/timandy/routine/tree/v1.0.6) --- # v1.0.5 Release notes ### Features - Support go version range `go1.13` ~ `go1.18`(New support `go1.18`). ### Changes - Change license to `Apache-2.0`. - Upgrade dependencies to the latest version. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.5](https://github.com/timandy/routine/tree/v1.0.5) --- # v1.0.4 Release notes ### Features - Add zero-copy conversion method between `bytes` and `string`, see `bytesconv.Bytes()` and `bytesconv.String()` methods. ### Changes - Modify the garbage collection mechanism, remove `gcTimer`, no longer perform garbage collection through timers. - Store the context in the `g.labels` field of the coroutine structure which will be set to `nil` after coroutine ends. The context data will be collected at the next `GC`. - Use `go:linkname` to invoke assembly code `getg()` directly to improve performance. - Implement the `getGoidByStack()` method by invoke `http.http2curGoroutineID()`. - Remove api `AllGoids()` and `ForeachGoid()`. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.4](https://github.com/timandy/routine/tree/v1.0.4) --- # v1.0.3 Release notes ### Features - Support copy `Cloneable` objects to sub goroutine when create sub goroutines by `Go()`, `GoWait()` and `GoWaitResult()` methods. - Add api `ForeachGoid(func(goid int64))` to run a func for each goid. ### Changes - Support go version range `go1.13` ~ `go1.17`(Not support `go1.12` anymore). - Use segment locks to reduce competition and improve `ThreadLocal`'s `read`, `write` and `gc` performance. - Get all goids through `runtime.allgs` instead of `runtime.atomicAllG`, so `go1.13` ~ `go1.15` can also get all goids natively. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.3](https://github.com/timandy/routine/tree/v1.0.3) --- # v1.0.2 Release notes ### Bugs - Fix bug in `getAllGoidByStack()` method, Buffer may too small when dump all stack info. ### Features - Support initialize value when first get from `ThreadLocal`. - Add `StackError` to catch stack info. - Add `Feature` to wait goroutine finished or get result from goroutine. - Add api `NewThreadLocalWithInitial()`, `NewInheritableThreadLocal()` and `NewInheritableThreadLocalWithInitial()`. - Support Inherit values of `ThreadLocal` by `Go`, `GoWait()` and `GoWaitResult()`. ### Changes - Rename `LocalStorage` to `ThreadLocal`. - Remove api `Clear()`, `InheritContext()` and `RestoreContext()`. - Improve `gc` performance by reducing the number of for loops. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.2](https://github.com/timandy/routine/tree/v1.0.2) --- # v1.0.1 Release notes ### Features - Improve performance by use slice to store goroutine local values. - Optimize `clearDeadStore()` method. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.1](https://github.com/timandy/routine/tree/v1.0.1) --- # v1.0.0 Release notes `This is the first stable version available for production. It is highly recommended to upgrade to this version if you have used a previous version.` ### Bugs - Fix `NewLocalStorage()` always return the same value, so we can define multi `LocalStorage` instances. - Fix `NewLocalStorage()` clear other `LocalStorage`'s value. - Fix `RestoreContext()` not clear values when restore from empty `*ImmutableContext`. ### Features - Not force create `store` when invoke `Get()`, `Remove()`, `Clear()`, `BackupContext()` methods to reduce memory usage. ### Changes - Rename `InheritContext()` to `RestoreContext()`. - Rename `Del()` to `Remove()`. - Move Clear() method to `routine` package. # Links - Source code [https://github.com/timandy/routine/tree/v1.0.0](https://github.com/timandy/routine/tree/v1.0.0) --- # v0.0.2 Release notes ### Features - Support go version range `go1.12` ~ `go1.17`(New support `go1.17`). - Enable GitHub actions for continuous integration. ### Known Issues - `NewLocalStorage()` always return the same value. # Links - Source code [https://github.com/timandy/routine/tree/v0.0.2](https://github.com/timandy/routine/tree/v0.0.2) --- # v0.0.1 Release notes ### Features - Support go version range `go1.12` ~ `go1.16`. - Support `Goid()` to get current goroutine id. - Support `AllGoids` to get all goroutine ids. - Support `ThreadLocal` to save values ingo to goroutine. ### Known Issues - `NewLocalStorage()` always return the same value. # Links - Source code [https://github.com/timandy/routine/tree/v0.0.1](https://github.com/timandy/routine/tree/v0.0.1) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/CODE_OF_CONDUCT.md����������������������������������������������0000664�0000000�0000000�00000012154�15172351712�0022202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [Tim Andy](mailto:xuchonglei@126.com). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/CONTRIBUTING.md�������������������������������������������������0000664�0000000�0000000�00000001061�15172351712�0021627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Contributing - With issues: - Use the search tool before opening a new issue. - Please provide source code and commit sha if you found a bug. - Review existing issues and provide feedback or react to them. - With pull requests: - Open your pull request against `main` branch. - It should pass all tests in the available continuous integration systems such as GitHub Actions. - You should add/modify tests to cover your proposed code changes. - If your pull request contains a new feature, please document it on the README.md and README_zh.md. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/LICENSE���������������������������������������������������������0000664�0000000�0000000�00000026121�15172351712�0020407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2021-2025 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. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/README.md�������������������������������������������������������0000664�0000000�0000000�00000031661�15172351712�0020666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# routine [![Build Status](https://github.com/timandy/routine/actions/workflows/build.yml/badge.svg)](https://github.com/timandy/routine/actions) [![Codecov](https://codecov.io/gh/timandy/routine/branch/main/graph/badge.svg)](https://app.codecov.io/gh/timandy/routine) [![Go Report Card](https://goreportcard.com/badge/github.com/timandy/routine)](https://goreportcard.com/report/github.com/timandy/routine) [![Documentation](https://pkg.go.dev/badge/github.com/timandy/routine.svg)](https://pkg.go.dev/github.com/timandy/routine) [![Release](https://img.shields.io/github/release/timandy/routine.svg)](https://github.com/timandy/routine/releases) [![License](https://img.shields.io/github/license/timandy/routine.svg)](https://github.com/timandy/routine/blob/main/LICENSE) > [中文版](README_zh.md) `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. # :house: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. # :loudspeaker:Update Notice :fire:**Version `1.1.5` introduces a new static mode.** - :rocket:Performance improved by over `20%`. - :rocket:Memory access is now safer. - :exclamation:The compile command requires additional parameters `-a -toolexec='routinex -v'`. For more details, visit: [RoutineX Compiler](https://github.com/timandy/routinex) # :hammer_and_wrench:Usage & Demo This chapter briefly introduces how to install and use the `routine` library. ## Install ```bash go get github.com/timandy/routine ``` ## Use `goid` The following code simply demonstrates the use of `routine.Goid()`: ```go 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`: ```text cur goid: 1 sub goid: 6 ``` ## Use `ThreadLocal` The following code briefly demonstrates `ThreadLocal`'s creation, setting, getting, spreading across coroutines, etc.: ```go 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: ```text 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 ``` # :books:API This chapter introduces in detail all the interfaces encapsulated by the `routine` library, as well as their core functions and implementation methods. ## `Goid() uint64` 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](https://pkg.go.dev/github.com/timandy/routine#section-documentation) # :wastebasket: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. # :globe_with_meridians: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 # :pray:Thanks Thanks to all [contributors](https://github.com/timandy/routine/graphs/contributors) for their contributions! # :scroll:*License* `routine` is released under the [Apache License 2.0](LICENSE). ``` Copyright 2021-2025 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. ``` �������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/README_zh.md����������������������������������������������������0000664�0000000�0000000�00000027630�15172351712�0021370�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# routine [![Build Status](https://github.com/timandy/routine/actions/workflows/build.yml/badge.svg)](https://github.com/timandy/routine/actions) [![Codecov](https://codecov.io/gh/timandy/routine/branch/main/graph/badge.svg)](https://app.codecov.io/gh/timandy/routine) [![Go Report Card](https://goreportcard.com/badge/github.com/timandy/routine)](https://goreportcard.com/report/github.com/timandy/routine) [![Documentation](https://pkg.go.dev/badge/github.com/timandy/routine.svg)](https://pkg.go.dev/github.com/timandy/routine) [![Release](https://img.shields.io/github/release/timandy/routine.svg)](https://github.com/timandy/routine/releases) [![License](https://img.shields.io/github/license/timandy/routine.svg)](https://github.com/timandy/routine/blob/main/LICENSE) > [English Version](README.md) `routine`封装并提供了一些易用、无竞争、高性能的`goroutine`上下文访问接口,它可以帮助你更优雅地访问协程上下文信息。 # :house:介绍 `Golang`语言从设计之初,就一直在不遗余力地向开发者屏蔽协程上下文的概念,包括协程`goid`的获取、进程内部协程状态、协程上下文存储等。 如果你使用过其他语言如`C++`、`Java`等,那么你一定很熟悉`ThreadLocal`,而在开始使用`Golang`之后,你一定会为缺少类似`ThreadLocal`的便捷功能而深感困惑与苦恼。 当然你可以选择使用`Context`,让它携带着全部上下文信息,在所有函数的第一个输入参数中出现,然后在你的系统中到处穿梭。 而`routine`的核心目标就是开辟另一条路:将`goroutine local storage`引入`Golang`世界。 # :loudspeaker:更新提示 :fire:**`1.1.5`版本引入了全新的静态模式。** - :rocket:性能提升超过`20%`。 - :rocket:内存访问变得更加安全。 - :exclamation:编译命令需要额外的参数`-a -toolexec='routinex -v'`。 详细信息,请访问:[RoutineX 编译器](https://github.com/timandy/routinex) # :hammer_and_wrench:使用演示 此章节简要介绍如何安装与使用`routine`库。 ## 安装 ```bash go get github.com/timandy/routine ``` ## 使用`goid` 以下代码简单演示了`routine.Goid()`的使用: ```go 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) }() // 等待子协程执行完。 time.Sleep(time.Second) } ``` 此例中`main`函数启动了一个新的协程,因此`Goid()`返回了主协程`1`和子协程`6`: ```text cur goid: 1 sub goid: 6 ``` ## 使用`ThreadLocal` 以下代码简单演示了`ThreadLocal`的创建、设置、获取、跨协程传播等: ```go 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()) // 子协程无法读取之前赋值的“hello world”。 go func() { fmt.Println("threadLocal in goroutine:", threadLocal.Get()) fmt.Println("inheritableThreadLocal in goroutine:", inheritableThreadLocal.Get()) }() // 但是,可以通过 Go/GoWait/GoWaitResult 函数启动一个新的子协程,当前协程的所有可继承变量都可以自动传递。 routine.Go(func() { fmt.Println("threadLocal in goroutine by Go:", threadLocal.Get()) fmt.Println("inheritableThreadLocal in goroutine by Go:", inheritableThreadLocal.Get()) }) // 也可以通过 WrapTask/WrapWaitTask/WrapWaitResultTask 函数创建一个任务,当前协程的所有可继承变量都可以被自动捕获。 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() // 等待子协程执行完。 time.Sleep(time.Second) } ``` 执行结果为: ```text 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 ``` # :books:API文档 此章节详细介绍了`routine`库封装的全部接口,以及它们的核心功能、实现方式等。 ## `Goid() uint64` 获取当前`goroutine`的`goid`。 在`386`、`amd64`、`armv6`、`armv7`、`arm64`、`loong64`、`mips`、`mipsle`、`mips64`、`mips64le`、`ppc64`、`ppc64le`、`riscv64`、`s390x`、`wasm`架构下通过汇编代码直接获取,此操作性能极高,耗时通常只相当于`rand.Int()`的五分之一。 ## `NewThreadLocal[T any]() ThreadLocal[T]` 创建一个新的`ThreadLocal[T]`实例,其存储的初始值为类型`T`的默认值。 ## `NewThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T]` 创建一个新的`ThreadLocal[T]`实例,其存储的初始值为方法`supplier()`的返回值。 ## `NewInheritableThreadLocal[T any]() ThreadLocal[T]` 创建一个新的`ThreadLocal[T]`实例,其存储的初始值为类型`T`的默认值。 当通过`Go()`、`GoWait()`或`GoWaitResult()`启动新协程时,当前协程的值会被复制到新协程。 当通过`WrapTask()`、`WrapWaitTask()`或`WrapWaitResultTask()`创建任务时,当前协程的值会被捕获。 ## `NewInheritableThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T]` 创建一个新的`ThreadLocal[T]`实例,其存储的初始值为方法`supplier()`的返回值。 当通过`Go()`、`GoWait()`或`GoWaitResult()`启动新协程时,当前协程的值会被复制到新协程。 当通过`WrapTask()`、`WrapWaitTask()`或`WrapWaitResultTask()`创建任务时,当前协程的值会被捕获。 ## `WrapTask(fun Runnable) FutureTask[any]` 创建一个新任务,并捕获当前协程的`inheritableThreadLocals`。 此函数返回一个`FutureTask`实例,但返回的任务不会自动运行。 你可以通过`FutureTask.Run()`方法在子协程或协程池中运行它,通过`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法等待任务执行完毕。 任务执行时的任何`panic`都会被捕获并打印错误堆栈,在调用`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法时`panic`会被再次抛出。 ## `WrapWaitTask(fun CancelRunnable) FutureTask[any]` 创建一个新任务,并捕获当前协程的`inheritableThreadLocals`。 此函数返回一个`FutureTask`实例,但返回的任务不会自动运行。 你可以通过`FutureTask.Run()`方法在子协程或协程池中运行它,通过`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法等待任务执行完毕。 任务执行时的任何`panic`都会被捕获,在调用`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法时`panic`会被再次抛出。 ## `WrapWaitResultTask[TResult any](fun CancelCallable[TResult]) FutureTask[TResult]` 创建一个新任务,并捕获当前协程的`inheritableThreadLocals`。 此函数返回一个`FutureTask`实例,但返回的任务不会自动运行。 你可以通过`FutureTask.Run()`方法在子协程或协程池中运行它,通过`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法等待任务执行完毕并获取结果。 任务执行时的任何`panic`都会被捕获,在调用`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法时`panic`会被再次抛出。 ## `Go(fun Runnable)` 启动一个新的协程,同时自动将当前协程的全部上下文`inheritableThreadLocals`数据复制至新协程。 子协程执行时的任何`panic`都会被捕获并自动打印堆栈。 ## `GoWait(fun CancelRunnable) FutureTask[any]` 启动一个新的协程,同时自动将当前协程的全部上下文`inheritableThreadLocals`数据复制至新协程。 可以通过返回值的`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法等待子协程执行完毕。 子协程执行时的任何`panic`都会被捕获并在调用`FutureTask.Get()`或`FutureTask.GetWithTimeout()`时再次抛出。 ## `GoWaitResult[TResult any](fun CancelCallable[TResult]) FutureTask[TResult]` 启动一个新的协程,同时自动将当前协程的全部上下文`inheritableThreadLocals`数据复制至新协程。 可以通过返回值的`FutureTask.Get()`或`FutureTask.GetWithTimeout()`方法等待子协程执行完毕并获取返回值。 子协程执行时的任何`panic`都会被捕获并在调用`FutureTask.Get()`或`FutureTask.GetWithTimeout()`时再次抛出。 [更多API文档](https://pkg.go.dev/github.com/timandy/routine#section-documentation) # :wastebasket:垃圾回收 `routine`为每个协程分配了一个`thread`结构,它存储了协程相关的上下文变量信息。 指向该结构的指针存储在协程结构的`g.labels`字段上。 当协程执行完毕退出时,`g.labels`将被设置为`nil`,不再引用`thread`结构。 `thread`结构将在下次`GC`时被回收。 如果`thread`中存储的数据也没有额外被引用,这些数据将被一并回收。 # :globe_with_meridians:支持网格 | | **`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`** | | ✅:支持 # :pray:鸣谢 感谢所有[贡献者](https://github.com/timandy/routine/graphs/contributors)的贡献! # :scroll:*许可证* `routine`是在 [Apache License 2.0](LICENSE) 下发布的。 ``` Copyright 2021-2025 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. ``` ��������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/SECURITY.md�����������������������������������������������������0000664�0000000�0000000�00000001736�15172351712�0021200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Security Policy ## Supported Versions At the moment, only the latest commit on the `main` branch will be supported for security vulnerabilities. | **Branch** | **Supported** | |:----------:|:-------------:| | `main` | ✅ | ## Reporting a Vulnerability **Please do not report security vulnerabilities through public GitHub issues.** If you found a security vulnerability in the current repository, please send a mail to [Tim Andy](mailto:xuchonglei@126.com). You should get a reply within *72 hours* that we have received your report and a tentative [CVSS](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator) score. We will do a preliminary analysis to confirm that the vulnerability is a plausible claim and decline the report otherwise. If possible, please include: 1. reproducible steps on how to trigger the vulnerability. 2. a description on why you are convinced that it exists. 3. any information you may have on active exploitation of the vulnerability. ����������������������������������golang-github-timandy-routine-1.1.6/VERSION���������������������������������������������������������0000664�0000000�0000000�00000000006�15172351712�0020444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������v1.1.6��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_cloneable.go������������������������������������������������0000664�0000000�0000000�00000000236�15172351712�0022505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine // Cloneable interface to support copy itself. type Cloneable interface { // Clone create and returns a copy of this object. Clone() any } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_cloneable_test.go�������������������������������������������0000664�0000000�0000000�00000002220�15172351712�0023537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestCloneable(t *testing.T) { //struct can not be cast to interface var value any = personCloneable{Id: 1, Name: "Hello"} _, ok := value.(Cloneable) assert.False(t, ok) //pointer can be cast to interface var pointer any = &personCloneable{Id: 1, Name: "Hello"} _, ok2 := pointer.(Cloneable) assert.True(t, ok2) //nil pointer can be cast to interface pointer = (*personCloneable)(nil) cloneable, ok3 := pointer.(Cloneable) assert.True(t, ok3) assert.True(t, cloneable != nil) } func TestCloneable_Clone(t *testing.T) { //clone struct pc := &personCloneable{Id: 1, Name: "Hello"} assert.NotSame(t, pc, pc.Clone()) assert.Equal(t, *pc, *(pc.Clone().(*personCloneable))) //copy pointer pcs := make([]*personCloneable, 1) pcs[0] = pc pcs2 := make([]*personCloneable, 1) copy(pcs2, pcs) assert.Same(t, pc, pcs2[0]) //clone nil panic assert.Panics(t, func() { pc2 := (*personCloneable)(nil) _ = pc2.Clone() }) } type personCloneable struct { Id int Name string } func (p *personCloneable) Clone() any { return &personCloneable{Id: p.Id, Name: p.Name} } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_error.go����������������������������������������������������0000664�0000000�0000000�00000003066�15172351712�0021716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine // RuntimeError runtime error with stack info. type RuntimeError interface { // Goid returns the goid of the coroutine that created the current error. Goid() uint64 // Gopc returns the pc of go statement that created the current error coroutine. Gopc() uintptr // Message returns the detail message string of this error. Message() string // StackTrace returns an array of stack trace elements, each representing one stack frame. StackTrace() []uintptr // Cause returns the cause of this error or nil if the cause is nonexistent or unknown. Cause() RuntimeError // Error returns a short description of this error. Error() string } // NewRuntimeError create a new RuntimeError instance. func NewRuntimeError(cause any) RuntimeError { goid, gopc, msg, stackTrace, innerErr := runtimeErrorNew(cause) return &runtimeError{goid: goid, gopc: gopc, message: msg, stackTrace: stackTrace, cause: innerErr} } // NewRuntimeErrorWithMessage create a new RuntimeError instance. func NewRuntimeErrorWithMessage(message string) RuntimeError { goid, gopc, msg, stackTrace, innerErr := runtimeErrorNewWithMessage(message) return &runtimeError{goid: goid, gopc: gopc, message: msg, stackTrace: stackTrace, cause: innerErr} } // NewRuntimeErrorWithMessageCause create a new RuntimeError instance. func NewRuntimeErrorWithMessageCause(message string, cause any) RuntimeError { goid, gopc, msg, stackTrace, innerErr := runtimeErrorNewWithMessageCause(message, cause) return &runtimeError{goid: goid, gopc: gopc, message: msg, stackTrace: stackTrace, cause: innerErr} } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_error_test.go�����������������������������������������������0000664�0000000�0000000�00000047472�15172351712�0022766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "errors" "runtime" "runtime/debug" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestNewRuntimeError_Nil(t *testing.T) { err := NewRuntimeError(nil) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_EmptyString(t *testing.T) { err := NewRuntimeError("") assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_WhiteSpaceString(t *testing.T) { err := NewRuntimeError("\t") assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NormalString(t *testing.T) { err := NewRuntimeError("this is error message") assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NilError(t *testing.T) { var cause error err := NewRuntimeError(cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NormalError(t *testing.T) { err := NewRuntimeError(errors.New("this is error message")) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NilRuntimeError(t *testing.T) { var cause RuntimeError err := NewRuntimeError(cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NormalRuntimeError(t *testing.T) { cause := NewRuntimeError("this is inner message") err := NewRuntimeError(cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Same(t, cause, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " ---> RuntimeError: this is inner message", lines[1]) } func TestNewRuntimeError_NilValue(t *testing.T) { var cause *person err := NewRuntimeError(cause) assertGoidGopc(t, err) assert.Equal(t, "<nil>", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: <nil>", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeError_NormalValue(t *testing.T) { cause := person{Id: 1, Name: "Tim"} err := NewRuntimeError(cause) assertGoidGopc(t, err) assert.Equal(t, "{1 Tim}", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: {1 Tim}", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessage_EmptyString(t *testing.T) { err := NewRuntimeErrorWithMessage("") assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessage_WhiteSpaceString(t *testing.T) { err := NewRuntimeErrorWithMessage("\t") assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessage_NormalString(t *testing.T) { err := NewRuntimeErrorWithMessage("this is error message") assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_Nil(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", nil) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_EmptyString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", "") assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_WhiteSpaceString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", "\t") assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NormalString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", "this is error message") assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NilError(t *testing.T) { var cause error err := NewRuntimeErrorWithMessageCause("", cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NormalError(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", errors.New("this is error message")) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NilRuntimeError(t *testing.T) { var cause RuntimeError err := NewRuntimeErrorWithMessageCause("", cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NormalRuntimeError(t *testing.T) { cause := NewRuntimeError("this is inner message") err := NewRuntimeErrorWithMessageCause("", cause) assertGoidGopc(t, err) assert.Equal(t, "", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Same(t, cause, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError", lines[0]) assert.Equal(t, " ---> RuntimeError: this is inner message", lines[1]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NilValue(t *testing.T) { var cause *person err := NewRuntimeErrorWithMessageCause("", cause) assertGoidGopc(t, err) assert.Equal(t, "<nil>", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: <nil>", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_EmptyString_NormalValue(t *testing.T) { cause := person{Id: 1, Name: "Tim"} err := NewRuntimeErrorWithMessageCause("", cause) assertGoidGopc(t, err) assert.Equal(t, "{1 Tim}", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: {1 Tim}", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_Nil(t *testing.T) { err := NewRuntimeErrorWithMessageCause("\t", nil) assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_EmptyString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("\t", "") assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_WhiteSpaceString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("\t", "\t") assertGoidGopc(t, err) assert.Equal(t, "\t - \t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t - \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NormalString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("\t", "this is error message") assertGoidGopc(t, err) assert.Equal(t, "\t - this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t - this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NilError(t *testing.T) { var cause error err := NewRuntimeErrorWithMessageCause("\t", cause) assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NormalError(t *testing.T) { err := NewRuntimeErrorWithMessageCause("\t", errors.New("this is error message")) assertGoidGopc(t, err) assert.Equal(t, "\t - this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t - this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NilRuntimeError(t *testing.T) { var cause RuntimeError err := NewRuntimeErrorWithMessageCause("\t", cause) assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NormalRuntimeError(t *testing.T) { cause := NewRuntimeError("this is inner message") err := NewRuntimeErrorWithMessageCause("\t", cause) assertGoidGopc(t, err) assert.Equal(t, "\t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Same(t, cause, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t", lines[0]) assert.Equal(t, " ---> RuntimeError: this is inner message", lines[1]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NilValue(t *testing.T) { var cause *person err := NewRuntimeErrorWithMessageCause("\t", cause) assertGoidGopc(t, err) assert.Equal(t, "\t - <nil>", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t - <nil>", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_WhiteSpaceString_NormalValue(t *testing.T) { cause := person{Id: 1, Name: "Tim"} err := NewRuntimeErrorWithMessageCause("\t", cause) assertGoidGopc(t, err) assert.Equal(t, "\t - {1 Tim}", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: \t - {1 Tim}", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_Nil(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", nil) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_EmptyString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", "") assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_WhiteSpaceString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", "\t") assertGoidGopc(t, err) assert.Equal(t, "this is error message - \t", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message - \t", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NormalString(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", "this is error message") assertGoidGopc(t, err) assert.Equal(t, "this is error message - this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message - this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NilError(t *testing.T) { var cause error err := NewRuntimeErrorWithMessageCause("this is error message", cause) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NormalError(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", errors.New("this is error message2")) assertGoidGopc(t, err) assert.Equal(t, "this is error message - this is error message2", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message - this is error message2", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NilRuntimeError(t *testing.T) { var cause RuntimeError err := NewRuntimeErrorWithMessageCause("this is error message", cause) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NormalRuntimeError(t *testing.T) { cause := NewRuntimeError("this is inner message") err := NewRuntimeErrorWithMessageCause("this is error message", cause) assertGoidGopc(t, err) assert.Equal(t, "this is error message", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Same(t, cause, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message", lines[0]) assert.Equal(t, " ---> RuntimeError: this is inner message", lines[1]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NilValue(t *testing.T) { var cause *person err := NewRuntimeErrorWithMessageCause("this is error message", cause) assertGoidGopc(t, err) assert.Equal(t, "this is error message - <nil>", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message - <nil>", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func TestNewRuntimeErrorWithMessageCause_NormalString_NormalValue(t *testing.T) { cause := person{Id: 1, Name: "Tim"} err := NewRuntimeErrorWithMessageCause("this is error message", cause) assertGoidGopc(t, err) assert.Equal(t, "this is error message - {1 Tim}", err.Message()) assert.Greater(t, len(err.StackTrace()), 1) assert.Nil(t, err.Cause()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, "RuntimeError: this is error message - {1 Tim}", lines[0]) assert.Equal(t, " at ", lines[1][:6]) } func assertGoidGopc(t *testing.T, err RuntimeError) { assert.Equal(t, Goid(), err.Goid()) assert.NotNil(t, runtime.FuncForPC(err.Gopc()-1)) } //=== // BenchmarkDebugStack-8 301474 3400 ns/op 1024 B/op 1 allocs/op func BenchmarkDebugStack(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = debug.Stack() } } // BenchmarkRuntimeError-8 342477 3344 ns/op 2840 B/op 15 allocs/op func BenchmarkRuntimeError(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewRuntimeError(nil).Error() } } // BenchmarkRuntimeErrorWithMessage-8 337117 3300 ns/op 2872 B/op 15 allocs/op func BenchmarkRuntimeErrorWithMessage(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewRuntimeErrorWithMessage("").Error() } } // BenchmarkRuntimeErrorWithMessageCause-8 350422 3362 ns/op 2872 B/op 15 allocs/op func BenchmarkRuntimeErrorWithMessageCause(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewRuntimeErrorWithMessageCause("", nil).Error() } } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_future_task.go����������������������������������������������0000664�0000000�0000000�00000004506�15172351712�0023121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import "time" // FutureCallable provides a future function that returns a value of type TResult. type FutureCallable[TResult any] func(task FutureTask[TResult]) TResult // CancelToken propagates notification that operations should be canceled. type CancelToken interface { // IsCanceled returns true if task was canceled. IsCanceled() bool // Cancel notifies the waiting coroutine that the task has canceled and returns stack information. Cancel() } // FutureTask provide a way to wait for the sub-coroutine to finish executing, get the return value of the sub-coroutine, and catch the sub-coroutine panic. type FutureTask[TResult any] interface { // IsDone returns true if completed in any fashion: normally, exceptionally or via cancellation. IsDone() bool // IsCanceled returns true if task was canceled. IsCanceled() bool // IsFailed returns true if completed exceptionally. IsFailed() bool // Complete notifies the waiting coroutine that the task has completed normally and returns the execution result. Complete(result TResult) // Cancel notifies the waiting coroutine that the task has canceled and returns stack information. Cancel() // Fail notifies the waiting coroutine that the task has terminated due to panic and returns stack information. Fail(error any) // Get return the execution result of the sub-coroutine, if there is no result, return nil. // If task is canceled, a panic with cancellation will be raised. // If panic is raised during the execution of the sub-coroutine, it will be raised again at this time. Get() TResult // GetWithTimeout return the execution result of the sub-coroutine, if there is no result, return nil. // If task is canceled, a panic with cancellation will be raised. // If panic is raised during the execution of the sub-coroutine, it will be raised again at this time. // If the deadline is reached, a panic with timeout error will be raised. GetWithTimeout(timeout time.Duration) TResult // Run execute the task, the method can be called repeatedly, but the task will only execute once. Run() } // NewFutureTask Create a new instance. func NewFutureTask[TResult any](callable FutureCallable[TResult]) FutureTask[TResult] { if callable == nil { panic("callable can not be nil.") } task := &futureTask[TResult]{callable: callable} task.await.Add(1) return task } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_future_task_test.go�����������������������������������������0000664�0000000�0000000�00000001524�15172351712�0024155�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestFutureCallable(t *testing.T) { var futureCallable FutureCallable[string] = func(task FutureTask[string]) string { return "Hello" } assert.Equal(t, "Hello", futureCallable(nil)) // var fun func(FutureTask[string]) string = futureCallable assert.Equal(t, "Hello", fun(nil)) } func TestCancelToken(t *testing.T) { task := NewFutureTask[any](func(task FutureTask[any]) any { return nil }) token, ok := task.(CancelToken) assert.Same(t, task, token) assert.True(t, ok) } func TestNewFutureTask(t *testing.T) { assert.Panics(t, func() { NewFutureTask[any](nil) }) // task := NewFutureTask[any](func(task FutureTask[any]) any { return nil }) assert.NotNil(t, task) // p, ok := task.(*futureTask[any]) assert.Same(t, p, task) assert.True(t, ok) } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_goid.go�����������������������������������������������������0000664�0000000�0000000�00000000160�15172351712�0021477�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine // Goid return the current goroutine's unique id. func Goid() uint64 { return getg().goid() } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_goid_test.go������������������������������������������������0000664�0000000�0000000�00000000650�15172351712�0022542�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestGoid(t *testing.T) { assert.NotEqual(t, 0, Goid()) assert.Equal(t, Goid(), Goid()) } //=== // BenchmarkGoid-8 331324310 3.589 ns/op 0 B/op 0 allocs/op func BenchmarkGoid(b *testing.B) { b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _ = Goid() } } ����������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_routine.go��������������������������������������������������0000664�0000000�0000000�00000007414�15172351712�0022253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine // Runnable provides a function without return values. type Runnable func() // Callable provides a function that returns a value of type TResult. type Callable[TResult any] func() TResult // CancelRunnable provides a cancellable function without return values. type CancelRunnable func(token CancelToken) // CancelCallable provides a cancellable function that returns a value of type TResult. type CancelCallable[TResult any] func(token CancelToken) TResult // WrapTask 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. func WrapTask(fun Runnable) FutureTask[any] { ctx := createInheritedMap() callable := inheritedTask{context: ctx, function: fun}.run return NewFutureTask[any](callable) } // WrapWaitTask 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. func WrapWaitTask(fun CancelRunnable) FutureTask[any] { ctx := createInheritedMap() callable := inheritedWaitTask{context: ctx, function: fun}.run return NewFutureTask[any](callable) } // WrapWaitResultTask 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. func WrapWaitResultTask[TResult any](fun CancelCallable[TResult]) FutureTask[TResult] { ctx := createInheritedMap() callable := inheritedWaitResultTask[TResult]{context: ctx, function: fun}.run return NewFutureTask[TResult](callable) } // Go starts a new goroutine, and copy inheritableThreadLocals from current goroutine. // This function will auto invoke the func and print error stack when panic occur in goroutine. func Go(fun Runnable) { task := WrapTask(fun) go task.Run() } // GoWait starts a new goroutine, and copy inheritableThreadLocals from current goroutine. // This function will auto invoke the func and return a FutureTask instance, so we can wait by FutureTask.Get or FutureTask.GetWithTimeout method. // If panic occur in goroutine, The panic will be trigger again when calling FutureTask.Get or FutureTask.GetWithTimeout method. func GoWait(fun CancelRunnable) FutureTask[any] { task := WrapWaitTask(fun) go task.Run() return task } // GoWaitResult starts a new goroutine, and copy inheritableThreadLocals from current goroutine. // This function will auto invoke the func and return a FutureTask instance, so we can wait and get result by FutureTask.Get or FutureTask.GetWithTimeout method. // If panic occur in goroutine, The panic will be trigger again when calling FutureTask.Get or FutureTask.GetWithTimeout method. func GoWaitResult[TResult any](fun CancelCallable[TResult]) FutureTask[TResult] { task := WrapWaitResultTask(fun) go task.Run() return task } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_routine_test.go���������������������������������������������0000664�0000000�0000000�00000057336�15172351712�0023322�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "fmt" "io" "os" "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func TestRunnable(t *testing.T) { count := 0 var runnable Runnable = func() { count++ } runnable() assert.Equal(t, 1, count) // var fun func() = runnable fun() assert.Equal(t, 2, count) } func TestCallable(t *testing.T) { var callable Callable[string] = func() string { return "Hello" } assert.Equal(t, "Hello", callable()) // var fun func() string = callable assert.Equal(t, "Hello", fun()) } func TestCancelRunnable(t *testing.T) { count := 0 var cancelRunnable CancelRunnable = func(token CancelToken) { count++ } cancelRunnable(nil) assert.Equal(t, 1, count) // var fun func(CancelToken) = cancelRunnable fun(nil) assert.Equal(t, 2, count) } func TestCancelCallable(t *testing.T) { var cancelCallable CancelCallable[string] = func(token CancelToken) string { return "Hello" } assert.Equal(t, "Hello", cancelCallable(nil)) // var fun func(CancelToken) string = cancelCallable assert.Equal(t, "Hello", fun(nil)) } func TestWrapTask_NoContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapTask(func() { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { task.Run() assert.Equal(t, "", tls.Get()) assert.Equal(t, "", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapTask_HasContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapTask(func() { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { tls.Set("你好") tlsInherit.Set("inherit 你好") task.Run() assert.Equal(t, "你好", tls.Get()) assert.Equal(t, "inherit 你好", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapTask_Complete_ThenFail(t *testing.T) { tracker := NewFileTracker(os.Stdout) tracker.Begin() defer tracker.End() // run := false wg := &sync.WaitGroup{} wg.Add(1) wg2 := &sync.WaitGroup{} wg2.Add(1) wg3 := &sync.WaitGroup{} wg3.Add(1) task := WrapTask(func() { wg.Done() //1 wg2.Wait() //4 run = true wg3.Done() //5 panic(1) }) go task.Run() wg.Wait() //2 task.Complete(nil) assert.Nil(t, task.Get()) wg2.Done() //3 wg3.Wait() //6 assert.True(t, task.IsDone()) assert.False(t, task.IsFailed()) assert.False(t, task.IsCanceled()) assert.True(t, run) // time.Sleep(10 * time.Millisecond) assert.Equal(t, "", tracker.Value()) } func TestWrapWaitTask_NoContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitTask(func(token CancelToken) { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { task.Run() assert.Equal(t, "", tls.Get()) assert.Equal(t, "", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) assert.Nil(t, task.Get()) assert.True(t, wrappedRun) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapWaitTask_NoContext_Cancel(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitTask(func(token CancelToken) { for i := 0; i < 1000; i++ { if token.IsCanceled() { return } time.Sleep(1 * time.Millisecond) } wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { task.Run() assert.Equal(t, "", tls.Get()) assert.Equal(t, "", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) task.Cancel() assert.True(t, task.IsCanceled()) assert.Panics(t, func() { task.Get() }) assert.False(t, wrappedRun) wg.Wait() assert.False(t, wrappedRun) assert.True(t, run) } func TestWrapWaitTask_HasContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitTask(func(token CancelToken) { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { tls.Set("你好") tlsInherit.Set("inherit 你好") task.Run() assert.Equal(t, "你好", tls.Get()) assert.Equal(t, "inherit 你好", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) assert.Nil(t, task.Get()) assert.True(t, wrappedRun) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapWaitTask_HasContext_Cancel(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitTask(func(token CancelToken) { for i := 0; i < 1000; i++ { if token.IsCanceled() { return } time.Sleep(1 * time.Millisecond) } wrappedRun = true }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { tls.Set("你好") tlsInherit.Set("inherit 你好") task.Run() assert.Equal(t, "你好", tls.Get()) assert.Equal(t, "inherit 你好", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) task.Cancel() assert.True(t, task.IsCanceled()) assert.Panics(t, func() { task.Get() }) assert.False(t, wrappedRun) wg.Wait() assert.False(t, wrappedRun) assert.True(t, run) } func TestWrapWaitTask_Complete_ThenFail(t *testing.T) { tracker := NewFileTracker(os.Stdout) tracker.Begin() defer tracker.End() // run := false wg := &sync.WaitGroup{} wg.Add(1) wg2 := &sync.WaitGroup{} wg2.Add(1) wg3 := &sync.WaitGroup{} wg3.Add(1) task := WrapWaitTask(func(token CancelToken) { wg.Done() //1 wg2.Wait() //4 run = true wg3.Done() //5 panic(1) }) go task.Run() wg.Wait() //2 task.Complete(nil) assert.Nil(t, task.Get()) wg2.Done() //3 wg3.Wait() //6 assert.True(t, task.IsDone()) assert.False(t, task.IsFailed()) assert.False(t, task.IsCanceled()) assert.True(t, run) // time.Sleep(10 * time.Millisecond) assert.Equal(t, "", tracker.Value()) } func TestWrapWaitResultTask_NoContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitResultTask(func(token CancelToken) any { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true return 1 }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { task.Run() assert.Equal(t, "", tls.Get()) assert.Equal(t, "", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) assert.Equal(t, 1, task.Get()) assert.True(t, wrappedRun) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapWaitResultTask_NoContext_Cancel(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitResultTask(func(token CancelToken) any { for i := 0; i < 1000; i++ { if token.IsCanceled() { return 0 } time.Sleep(1 * time.Millisecond) } wrappedRun = true return 1 }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { task.Run() assert.Equal(t, "", tls.Get()) assert.Equal(t, "", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) task.Cancel() assert.True(t, task.IsCanceled()) assert.Panics(t, func() { task.Get() }) assert.False(t, wrappedRun) wg.Wait() assert.False(t, wrappedRun) assert.True(t, run) } func TestWrapWaitResultTask_HasContext(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitResultTask(func(token CancelToken) any { assert.Equal(t, "", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) tls.Set("世界") tlsInherit.Set("inherit 世界") assert.Equal(t, "世界", tls.Get()) assert.Equal(t, "inherit 世界", tlsInherit.Get()) wrappedRun = true return 1 }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { tls.Set("你好") tlsInherit.Set("inherit 你好") task.Run() assert.Equal(t, "你好", tls.Get()) assert.Equal(t, "inherit 你好", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) assert.Equal(t, 1, task.Get()) assert.True(t, wrappedRun) wg.Wait() assert.True(t, wrappedRun) assert.True(t, run) } func TestWrapWaitResultTask_HasContext_Cancel(t *testing.T) { run := false wrappedRun := false wg := &sync.WaitGroup{} wg.Add(1) tls := NewThreadLocal[string]() tlsInherit := NewInheritableThreadLocal[string]() tls.Set("hello") tlsInherit.Set("inherit hello") assert.Equal(t, "hello", tls.Get()) assert.Equal(t, "inherit hello", tlsInherit.Get()) task := WrapWaitResultTask(func(token CancelToken) any { for i := 0; i < 1000; i++ { if token.IsCanceled() { return 0 } time.Sleep(1 * time.Millisecond) } wrappedRun = true return 1 }) tls.Set("world") tlsInherit.Set("inherit world") assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) go func() { tls.Set("你好") tlsInherit.Set("inherit 你好") task.Run() assert.Equal(t, "你好", tls.Get()) assert.Equal(t, "inherit 你好", tlsInherit.Get()) run = true wg.Done() }() assert.Equal(t, "world", tls.Get()) assert.Equal(t, "inherit world", tlsInherit.Get()) task.Cancel() assert.True(t, task.IsCanceled()) assert.Panics(t, func() { task.Get() }) assert.False(t, wrappedRun) wg.Wait() assert.False(t, wrappedRun) assert.True(t, run) } func TestWrapWaitResultTask_Complete_ThenFail(t *testing.T) { tracker := NewFileTracker(os.Stdout) tracker.Begin() defer tracker.End() // run := false wg := &sync.WaitGroup{} wg.Add(1) wg2 := &sync.WaitGroup{} wg2.Add(1) wg3 := &sync.WaitGroup{} wg3.Add(1) task := WrapWaitResultTask(func(token CancelToken) any { wg.Done() //1 wg2.Wait() //4 run = true wg3.Done() //5 panic(1) }) go task.Run() wg.Wait() //2 task.Complete(nil) assert.Nil(t, task.Get()) wg2.Done() //3 wg3.Wait() //6 assert.True(t, task.IsDone()) assert.False(t, task.IsFailed()) assert.False(t, task.IsCanceled()) assert.True(t, run) // time.Sleep(10 * time.Millisecond) assert.Equal(t, "", tracker.Value()) } func TestGo_Error(t *testing.T) { tracker := NewFileTracker(os.Stdout) tracker.Begin() defer tracker.End() // run := false assert.NotPanics(t, func() { wg := &sync.WaitGroup{} wg.Add(1) Go(func() { run = true wg.Done() panic("error") }) wg.Wait() }) assert.True(t, run) // time.Sleep(10 * time.Millisecond) lines := strings.Split(tracker.Value(), newLine) assert.Equal(t, 7, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: error", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestGo_Error.")) assert.True(t, strings.HasSuffix(line, "api_routine_test.go:601")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.inheritedTask.run()")) assert.True(t, strings.HasSuffix(line, "routine.go:24")) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) assert.True(t, strings.HasSuffix(line, "future_task.go:108")) // line = lines[4] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[5] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.Go()")) assert.True(t, strings.HasSuffix(line, "api_routine.go:49")) // line = lines[6] assert.Equal(t, "", line) } func TestGo_Nil(t *testing.T) { assert.Nil(t, createInheritedMap()) // run := false wg := &sync.WaitGroup{} wg.Add(1) Go(func() { assert.Nil(t, createInheritedMap()) run = true wg.Done() }) wg.Wait() assert.True(t, run) } func TestGo_Value(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // inheritableTls := NewInheritableThreadLocal[string]() inheritableTls.Set("World") assert.Equal(t, "World", inheritableTls.Get()) // assert.NotNil(t, createInheritedMap()) // run := false wg := &sync.WaitGroup{} wg.Add(1) Go(func() { assert.NotNil(t, createInheritedMap()) // assert.Equal(t, "", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) // tls.Set("Hello2") assert.Equal(t, "Hello2", tls.Get()) // inheritableTls.Remove() assert.Equal(t, "", inheritableTls.Get()) // run = true wg.Done() }) wg.Wait() assert.True(t, run) // assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) } func TestGo_Cross(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // wg := &sync.WaitGroup{} wg.Add(1) Go(func() { assert.Equal(t, "", tls.Get()) wg.Done() }) wg.Wait() } func TestGoWait_Error(t *testing.T) { run := false task := GoWait(func(token CancelToken) { run = true panic("error") }) assert.Panics(t, func() { task.Get() }) assert.True(t, run) // defer func() { cause := recover() assert.NotNil(t, cause) assert.Implements(t, (*RuntimeError)(nil), cause) err := cause.(RuntimeError) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 6, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: error", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestGoWait_Error.")) assert.True(t, strings.HasSuffix(line, "api_routine_test.go:706")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.inheritedWaitTask.run()")) assert.True(t, strings.HasSuffix(line, "routine.go:44")) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) assert.True(t, strings.HasSuffix(line, "future_task.go:108")) // line = lines[4] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[5] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.GoWait()")) assert.True(t, strings.HasSuffix(line, "api_routine.go:57")) }() task.Get() } func TestGoWait_Nil(t *testing.T) { assert.Nil(t, createInheritedMap()) // run := false task := GoWait(func(token CancelToken) { assert.Nil(t, createInheritedMap()) run = true }) assert.Nil(t, task.Get()) assert.True(t, run) } func TestGoWait_Value(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // inheritableTls := NewInheritableThreadLocal[string]() inheritableTls.Set("World") assert.Equal(t, "World", inheritableTls.Get()) // assert.NotNil(t, createInheritedMap()) // run := false task := GoWait(func(token CancelToken) { assert.NotNil(t, createInheritedMap()) // assert.Equal(t, "", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) // tls.Set("Hello2") assert.Equal(t, "Hello2", tls.Get()) // inheritableTls.Remove() assert.Equal(t, "", inheritableTls.Get()) // run = true }) assert.Nil(t, task.Get()) assert.True(t, run) // assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) } func TestGoWait_Cross(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // GoWait(func(token CancelToken) { assert.Equal(t, "", tls.Get()) }).Get() } func TestGoWaitResult_Error(t *testing.T) { run := false task := GoWaitResult(func(token CancelToken) int { run = true if run { panic("error") } return 1 }) assert.Panics(t, func() { task.Get() }) assert.True(t, run) // defer func() { cause := recover() assert.NotNil(t, cause) assert.Implements(t, (*RuntimeError)(nil), cause) err := cause.(RuntimeError) lines := strings.Split(err.Error(), newLine) assert.True(t, len(lines) == 6 || len(lines) == 7) // line := lines[0] assert.Equal(t, "RuntimeError: error", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestGoWaitResult_Error.")) assert.True(t, strings.HasSuffix(line, "api_routine_test.go:806")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.inheritedWaitResultTask[...].run()")) assert.True(t, strings.HasSuffix(line, "routine.go:64")) // lineOffset := 0 if len(lines) == 7 { line = lines[3+lineOffset] lineOffset = 1 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.WrapWaitResultTask[...].func1()")) assert.True(t, strings.HasSuffix(line, "api_routine.go:41")) } // line = lines[3+lineOffset] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) assert.True(t, strings.HasSuffix(line, "future_task.go:108")) // line = lines[4+lineOffset] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[5+lineOffset] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.GoWaitResult[...]()")) assert.True(t, strings.HasSuffix(line, "api_routine.go:66")) }() task.Get() } func TestGoWaitResult_Nil(t *testing.T) { assert.Nil(t, createInheritedMap()) // run := false task := GoWaitResult(func(token CancelToken) bool { assert.Nil(t, createInheritedMap()) run = true return true }) assert.True(t, task.Get()) assert.True(t, run) } func TestGoWaitResult_Value(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // inheritableTls := NewInheritableThreadLocal[string]() inheritableTls.Set("World") assert.Equal(t, "World", inheritableTls.Get()) // assert.NotNil(t, createInheritedMap()) // run := false task := GoWaitResult(func(token CancelToken) bool { assert.NotNil(t, createInheritedMap()) // assert.Equal(t, "", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) // tls.Set("Hello2") assert.Equal(t, "Hello2", tls.Get()) // inheritableTls.Remove() assert.Equal(t, "", inheritableTls.Get()) // run = true return true }) assert.True(t, task.Get()) assert.True(t, run) // assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, "World", inheritableTls.Get()) } func TestGoWaitResult_Cross(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // result := GoWaitResult(func(token CancelToken) string { assert.Equal(t, "", tls.Get()) return tls.Get() }).Get() assert.Equal(t, "", result) } //=== type FileTracker struct { target *os.File oldValue os.File tempValue os.File } func NewFileTracker(target *os.File) *FileTracker { return &FileTracker{target: target, oldValue: *target} } func (f *FileTracker) Begin() { file, err := os.CreateTemp("", "go_test_*.txt") if err != nil { panic(err) } *f.target = *file f.tempValue = *file } func (f *FileTracker) End() { *f.target = f.oldValue if err := f.tempValue.Close(); err != nil { panic(err) } if err := os.Remove(f.tempValue.Name()); err != nil { panic(err) } } func (f *FileTracker) Value() string { if _, err := f.tempValue.Seek(0, io.SeekStart); err != nil { panic(err) } buff, err := io.ReadAll(&f.tempValue) if err != nil { panic(err) } return string(buff) } func TestFileTracker(t *testing.T) { origin := os.Stdout tracker := NewFileTracker(os.Stdout) tracker.Begin() _, _ = fmt.Fprintln(os.Stdout, "hello world") assert.Equal(t, "hello world\n", tracker.Value()) tracker.End() assert.Equal(t, "/dev/stdout", origin.Name()) assert.Same(t, origin, os.Stdout) } func TestFileTrackerRef(t *testing.T) { origin := os.Stdout tracker := NewFileTracker(os.Stdout) tracker.Begin() _, _ = fmt.Fprintln(origin, "hello world") assert.Equal(t, "hello world\n", tracker.Value()) tracker.End() assert.Equal(t, "/dev/stdout", origin.Name()) assert.Same(t, origin, os.Stdout) } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_thread_local.go���������������������������������������������0000664�0000000�0000000�00000004127�15172351712�0023205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine // ThreadLocal provides goroutine-local variables. type ThreadLocal[T any] interface { // Get returns the value in the current goroutine's local threadLocals or inheritableThreadLocals, if it was set before. Get() T // Set copy the value into the current goroutine's local threadLocals or inheritableThreadLocals. Set(value T) // Remove delete the value from the current goroutine's local threadLocals or inheritableThreadLocals. Remove() } // Supplier provides a function that returns a value of type T. type Supplier[T any] func() T // NewThreadLocal create and return a new ThreadLocal instance. // The initial value stored with the default value of type T. func NewThreadLocal[T any]() ThreadLocal[T] { return &threadLocal[T]{index: nextThreadLocalIndex()} } // NewThreadLocalWithInitial create and return a new ThreadLocal instance. // The initial value stored as the return value of the method supplier. func NewThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T] { return &threadLocal[T]{index: nextThreadLocalIndex(), supplier: supplier} } // NewInheritableThreadLocal create and return a new ThreadLocal instance. // The initial value stored with the default value of type T. // The value can be inherited to sub goroutines witch started by Go, GoWait, GoWaitResult methods. // The value can be captured to FutureTask which created by WrapTask, WrapWaitTask, WrapWaitResultTask methods. func NewInheritableThreadLocal[T any]() ThreadLocal[T] { return &inheritableThreadLocal[T]{index: nextInheritableThreadLocalIndex()} } // NewInheritableThreadLocalWithInitial create and return a new ThreadLocal instance. // The initial value stored as the return value of the method supplier. // The value can be inherited to sub goroutines witch started by Go, GoWait, GoWaitResult methods. // The value can be captured to FutureTask which created by WrapTask, WrapWaitTask, WrapWaitResultTask methods. func NewInheritableThreadLocalWithInitial[T any](supplier Supplier[T]) ThreadLocal[T] { return &inheritableThreadLocal[T]{index: nextInheritableThreadLocalIndex(), supplier: supplier} } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/api_thread_local_test.go����������������������������������������0000664�0000000�0000000�00000037122�15172351712�0024245�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "math/rand" "sync" "testing" "github.com/stretchr/testify/assert" ) const ( concurrency = 500 loopTimes = 200 ) func TestSupplier(t *testing.T) { var supplier Supplier[string] = func() string { return "Hello" } assert.Equal(t, "Hello", supplier()) // var fun func() string = supplier assert.Equal(t, "Hello", fun()) } //=== func TestNewThreadLocal_Single(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // tls2 := NewThreadLocal[int]() assert.Equal(t, "Hello", tls.Get()) tls2.Set(22) assert.Equal(t, 22, tls2.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "", tls.Get()) assert.Equal(t, 0, tls2.Get()) }) task.Get() } func TestNewThreadLocal_Multi(t *testing.T) { tls := NewThreadLocal[string]() tls2 := NewThreadLocal[int]() tls.Set("Hello") tls2.Set(22) assert.Equal(t, 22, tls2.Get()) assert.Equal(t, "Hello", tls.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "", tls.Get()) assert.Equal(t, 0, tls2.Get()) }) task.Get() } func TestNewThreadLocal_Concurrency(t *testing.T) { tls := NewThreadLocal[uint64]() tls2 := NewThreadLocal[uint64]() // tls2.Set(33) assert.Equal(t, uint64(33), tls2.Get()) // wg := &sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) Go(func() { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(0), tls2.Get()) v := rand.Uint64() v2 := rand.Uint64() for j := 0; j < loopTimes; j++ { tls.Set(v) tmp := tls.Get() assert.Equal(t, v, tmp) // tls2.Set(v2) tmp2 := tls2.Get() assert.Equal(t, v2, tmp2) } wg.Done() }) } wg.Wait() // task := GoWait(func(token CancelToken) { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(0), tls2.Get()) }) task.Get() } func TestNewThreadLocal_Interface(t *testing.T) { tls := NewThreadLocal[Cloneable]() tls2 := NewThreadLocal[Cloneable]() // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } func TestNewThreadLocal_Pointer(t *testing.T) { tls := NewThreadLocal[*personCloneable]() tls2 := NewThreadLocal[*personCloneable]() // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } //=== func TestNewThreadLocalWithInitial_Single(t *testing.T) { tls := NewThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Equal(t, "Hello", tls.Get()) // tls2 := NewThreadLocalWithInitial[int](func() int { return 22 }) assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 22, tls2.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 22, tls2.Get()) }) task.Get() } func TestNewThreadLocalWithInitial_Multi(t *testing.T) { tls := NewThreadLocalWithInitial[string](func() string { return "Hello" }) tls2 := NewThreadLocalWithInitial[int](func() int { return 22 }) tls.Set("Hello") tls2.Set(22) assert.Equal(t, 22, tls2.Get()) assert.Equal(t, "Hello", tls.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 22, tls2.Get()) }) task.Get() } func TestNewThreadLocalWithInitial_Concurrency(t *testing.T) { tls := NewThreadLocalWithInitial[any](func() any { return "Hello" }) tls2 := NewThreadLocalWithInitial[uint64](func() uint64 { return uint64(22) }) // tls2.Set(33) assert.Equal(t, uint64(33), tls2.Get()) // wg := &sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) Go(func() { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(22), tls2.Get()) v := rand.Uint64() v2 := rand.Uint64() for j := 0; j < loopTimes; j++ { tls.Set(v) tmp := tls.Get() assert.Equal(t, v, tmp.(uint64)) // tls2.Set(v2) tmp2 := tls2.Get() assert.Equal(t, v2, tmp2) } wg.Done() }) } wg.Wait() // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(22), tls2.Get()) }) task.Get() } func TestNewThreadLocalWithInitial_Interface(t *testing.T) { tls := NewThreadLocalWithInitial[Cloneable](func() Cloneable { return nil }) tls2 := NewThreadLocalWithInitial[Cloneable](func() Cloneable { return nil }) // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } func TestNewThreadLocalWithInitial_Pointer(t *testing.T) { tls := NewThreadLocalWithInitial[*personCloneable](func() *personCloneable { return nil }) tls2 := NewThreadLocalWithInitial[*personCloneable](func() *personCloneable { return nil }) // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } //=== func TestNewInheritableThreadLocal_Single(t *testing.T) { tls := NewInheritableThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) // tls2 := NewInheritableThreadLocal[int]() assert.Equal(t, "Hello", tls.Get()) tls2.Set(22) assert.Equal(t, 22, tls2.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 33, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocal_Multi(t *testing.T) { tls := NewInheritableThreadLocal[string]() tls2 := NewInheritableThreadLocal[int]() tls.Set("Hello") tls2.Set(22) assert.Equal(t, 22, tls2.Get()) assert.Equal(t, "Hello", tls.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 33, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocal_Concurrency(t *testing.T) { tls := NewInheritableThreadLocal[uint64]() tls2 := NewInheritableThreadLocal[uint64]() // tls2.Set(33) assert.Equal(t, uint64(33), tls2.Get()) // wg := &sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) Go(func() { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) v := rand.Uint64() v2 := rand.Uint64() for j := 0; j < loopTimes; j++ { tls.Set(v) tmp := tls.Get() assert.Equal(t, v, tmp) // tls2.Set(v2) tmp2 := tls2.Get() assert.Equal(t, v2, tmp2) } wg.Done() }) } wg.Wait() // task := GoWait(func(token CancelToken) { assert.Equal(t, uint64(0), tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocal_Interface(t *testing.T) { tls := NewInheritableThreadLocal[Cloneable]() tls2 := NewInheritableThreadLocal[Cloneable]() // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocal_Pointer(t *testing.T) { tls := NewInheritableThreadLocal[*personCloneable]() tls2 := NewInheritableThreadLocal[*personCloneable]() // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } //=== func TestNewInheritableThreadLocalWithInitial_Single(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Equal(t, "Hello", tls.Get()) // tls2 := NewInheritableThreadLocalWithInitial[int](func() int { return 22 }) assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 22, tls2.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 33, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocalWithInitial_Multi(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[string](func() string { return "Hello" }) tls2 := NewInheritableThreadLocalWithInitial[int](func() int { return 22 }) tls.Set("Hello") tls2.Set(22) assert.Equal(t, 22, tls2.Get()) assert.Equal(t, "Hello", tls.Get()) // tls2.Set(33) assert.Equal(t, 33, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, 33, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocalWithInitial_Concurrency(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[any](func() any { return "Hello" }) tls2 := NewInheritableThreadLocalWithInitial[uint64](func() uint64 { return uint64(22) }) // tls2.Set(33) assert.Equal(t, uint64(33), tls2.Get()) // wg := &sync.WaitGroup{} wg.Add(concurrency) for i := 0; i < concurrency; i++ { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) Go(func() { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) v := rand.Uint64() v2 := rand.Uint64() for j := 0; j < loopTimes; j++ { tls.Set(v) tmp := tls.Get() assert.Equal(t, v, tmp.(uint64)) // tls2.Set(v2) tmp2 := tls2.Get() assert.Equal(t, v2, tmp2) } wg.Done() }) } wg.Wait() // task := GoWait(func(token CancelToken) { assert.Equal(t, "Hello", tls.Get()) assert.Equal(t, uint64(33), tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocalWithInitial_Interface(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[Cloneable](func() Cloneable { return nil }) tls2 := NewInheritableThreadLocalWithInitial[Cloneable](func() Cloneable { return nil }) // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } func TestNewInheritableThreadLocalWithInitial_Pointer(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[*personCloneable](func() *personCloneable { return nil }) tls2 := NewInheritableThreadLocalWithInitial[*personCloneable](func() *personCloneable { return nil }) // assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(nil) tls2.Set(nil) assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // tls.Set(&personCloneable{Id: 1, Name: "Hello"}) tls2.Set(&personCloneable{Id: 1, Name: "Hello"}) assert.NotNil(t, tls.Get()) assert.NotNil(t, tls2.Get()) // tls.Remove() tls2.Remove() assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) // task := GoWait(func(token CancelToken) { assert.Nil(t, tls.Get()) assert.Nil(t, tls2.Get()) }) task.Get() } //=== // BenchmarkThreadLocal-8 13636471 94.17 ns/op 7 B/op 0 allocs/op func BenchmarkThreadLocal(b *testing.B) { tlsCount := 100 tlsSlice := make([]ThreadLocal[int], tlsCount) for i := 0; i < tlsCount; i++ { tlsSlice[i] = NewThreadLocal[int]() } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { index := i % tlsCount tls := tlsSlice[index] initValue := tls.Get() if initValue != 0 { b.Fail() } tls.Set(i) if tls.Get() != i { b.Fail() } tls.Remove() } } // BenchmarkThreadLocalWithInitial-8 13674153 86.76 ns/op 7 B/op 0 allocs/op func BenchmarkThreadLocalWithInitial(b *testing.B) { tlsCount := 100 tlsSlice := make([]ThreadLocal[int], tlsCount) for i := 0; i < tlsCount; i++ { index := i tlsSlice[i] = NewThreadLocalWithInitial[int](func() int { return index }) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { index := i % tlsCount tls := tlsSlice[index] initValue := tls.Get() if initValue != index { b.Fail() } tls.Set(i) if tls.Get() != i { b.Fail() } tls.Remove() } } // BenchmarkInheritableThreadLocal-8 13917819 84.27 ns/op 7 B/op 0 allocs/op func BenchmarkInheritableThreadLocal(b *testing.B) { tlsCount := 100 tlsSlice := make([]ThreadLocal[int], tlsCount) for i := 0; i < tlsCount; i++ { tlsSlice[i] = NewInheritableThreadLocal[int]() } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { index := i % tlsCount tls := tlsSlice[index] initValue := tls.Get() if initValue != 0 { b.Fail() } tls.Set(i) if tls.Get() != i { b.Fail() } tls.Remove() } } // BenchmarkInheritableThreadLocalWithInitial-8 13483130 90.03 ns/op 7 B/op 0 allocs/op func BenchmarkInheritableThreadLocalWithInitial(b *testing.B) { tlsCount := 100 tlsSlice := make([]ThreadLocal[int], tlsCount) for i := 0; i < tlsCount; i++ { index := i tlsSlice[i] = NewInheritableThreadLocalWithInitial[int](func() int { return index }) } b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { index := i % tlsCount tls := tlsSlice[index] initValue := tls.Get() if initValue != index { b.Fail() } tls.Set(i) if tls.Get() != i { b.Fail() } tls.Remove() } } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/error.go��������������������������������������������������������0000664�0000000�0000000�00000010475�15172351712�0021067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "bytes" "fmt" "reflect" "runtime" "strconv" "unicode" ) const ( newLine = "\n" innerErrorPrefix = " ---> " endOfInnerErrorStack = "--- End of inner error stack trace ---" endOfErrorStack = "--- End of error stack trace ---" wordAt = "at" wordIn = "in" wordCreatedBy = "created by" ) type runtimeError struct { goid uint64 gopc uintptr message string stackTrace []uintptr cause RuntimeError } func (re *runtimeError) Goid() uint64 { return re.goid } func (re *runtimeError) Gopc() uintptr { return re.gopc } func (re *runtimeError) Message() string { return re.message } func (re *runtimeError) StackTrace() []uintptr { return re.stackTrace } func (re *runtimeError) Cause() RuntimeError { return re.cause } func (re *runtimeError) Error() string { return runtimeErrorError(re) } func runtimeErrorNew(cause any) (goid uint64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) { runtimeErr, isRuntimeErr := cause.(RuntimeError) if !isRuntimeErr { if err, isErr := cause.(error); isErr { msg = err.Error() } else if cause != nil { msg = fmt.Sprint(cause) } } gp := getg() return gp.goid(), gp.gopc(), msg, captureStackTrace(2, 100), runtimeErr } func runtimeErrorNewWithMessage(message string) (goid uint64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) { gp := getg() return gp.goid(), gp.gopc(), message, captureStackTrace(2, 100), nil } func runtimeErrorNewWithMessageCause(message string, cause any) (goid uint64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) { runtimeErr, isRuntimeErr := cause.(RuntimeError) if !isRuntimeErr { causeMsg := "" if err, isErr := cause.(error); isErr { causeMsg = err.Error() } else if cause != nil { causeMsg = fmt.Sprint(cause) } if len(message) == 0 { message = causeMsg } else if len(causeMsg) != 0 { message += " - " + causeMsg } } gp := getg() return gp.goid(), gp.gopc(), message, captureStackTrace(2, 100), runtimeErr } func runtimeErrorError(re RuntimeError) string { builder := &bytes.Buffer{} runtimeErrorPrintStackTrace(re, builder) runtimeErrorPrintCreatedBy(re, builder) return builder.String() } func runtimeErrorPrintStackTrace(re RuntimeError, builder *bytes.Buffer) { builder.WriteString(runtimeErrorTypeName(re)) message := re.Message() if len(message) > 0 { builder.WriteString(": ") builder.WriteString(message) } cause := re.Cause() if cause != nil { builder.WriteString(newLine) builder.WriteString(innerErrorPrefix) runtimeErrorPrintStackTrace(cause, builder) builder.WriteString(newLine) builder.WriteString(" ") builder.WriteString(endOfInnerErrorStack) } stackTrace := re.StackTrace() if stackTrace != nil { savePoint := builder.Len() skippedPanic := false frames := runtime.CallersFrames(stackTrace) for { frame, more := frames.Next() if showFrame(frame.Function) { builder.WriteString(newLine) builder.WriteString(" ") builder.WriteString(wordAt) builder.WriteString(" ") builder.WriteString(frame.Function) builder.WriteString("() ") builder.WriteString(wordIn) builder.WriteString(" ") builder.WriteString(frame.File) builder.WriteString(":") builder.WriteString(strconv.Itoa(frame.Line)) } else if skipFrame(frame.Function, skippedPanic) { builder.Truncate(savePoint) skippedPanic = true } if !more { break } } } } func runtimeErrorPrintCreatedBy(re RuntimeError, builder *bytes.Buffer) { goid := re.Goid() if goid == 1 { return } pc := re.Gopc() frame, _ := runtime.CallersFrames([]uintptr{pc}).Next() if frame.Func == nil { return } builder.WriteString(newLine) builder.WriteString(" ") builder.WriteString(endOfErrorStack) builder.WriteString(newLine) builder.WriteString(" ") builder.WriteString(wordCreatedBy) builder.WriteString(" ") builder.WriteString(frame.Function) builder.WriteString("() ") builder.WriteString(wordIn) builder.WriteString(" ") builder.WriteString(frame.File) builder.WriteString(":") builder.WriteString(strconv.Itoa(frame.Line)) } func runtimeErrorTypeName(re RuntimeError) string { typeName := []rune(reflect.TypeOf(re).Elem().Name()) typeName[0] = unicode.ToUpper(typeName[0]) return string(typeName) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/error_test.go���������������������������������������������������0000664�0000000�0000000�00000033033�15172351712�0022121�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "errors" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestRuntimeError_Goid(t *testing.T) { goid := Goid() err := NewRuntimeError(nil) assert.Equal(t, goid, err.Goid()) task := GoWait(func(token CancelToken) { assert.Equal(t, goid, err.Goid()) assert.NotEqual(t, Goid(), err.Goid()) }) task.Get() } func TestRuntimeError_Gopc(t *testing.T) { gopc := getg().gopc() err := NewRuntimeError(nil) assert.Equal(t, gopc, err.Gopc()) task := GoWait(func(token CancelToken) { assert.Equal(t, gopc, err.Gopc()) assert.NotEqual(t, getg().gopc(), err.Gopc()) }) task.Get() } func TestRuntimeError_Message(t *testing.T) { err := NewRuntimeError(nil) assert.Equal(t, "", err.Message()) err2 := NewRuntimeError("Hello") assert.Equal(t, "Hello", err2.Message()) err3 := NewRuntimeError(&person{Id: 1, Name: "Tim"}) assert.Equal(t, "&{1 Tim}", err3.Message()) } func TestRuntimeError_StackTrace(t *testing.T) { err := NewRuntimeError(nil) stackTrace := err.StackTrace() capturedStackTrace := captureStackTrace(0, 200) for i := 1; i < len(stackTrace); i++ { assert.Equal(t, capturedStackTrace[i], stackTrace[i]) } } func TestRuntimeError_Panic_Panic(t *testing.T) { defer func() { cause := recover() assert.NotNil(t, cause) err := NewRuntimeError(cause) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 6, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: 1", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Panic_Panic.")) assert.True(t, strings.HasSuffix(line, "error_test.go:74")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Panic_Panic()")) assert.True(t, strings.HasSuffix(line, "error_test.go:77")) }() defer func() { if cause := recover(); cause != nil { panic(cause) } }() panic(1) } func TestRuntimeError_Cause(t *testing.T) { err := NewRuntimeError(nil) assert.Nil(t, err.Cause()) err2 := NewRuntimeError(errors.New("error")) assert.Nil(t, err2.Cause()) err3 := NewRuntimeError(&person{Id: 1, Name: "Tim"}) assert.Nil(t, err3.Cause()) err4 := NewRuntimeError(err) assert.Same(t, err, err4.Cause()) } func TestRuntimeError_Error_EmptyMessage_NilError(t *testing.T) { err := NewRuntimeErrorWithMessageCause("", nil) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 5, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NilError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:95")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[3] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[4] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestRuntimeError_Error_EmptyMessage_NormalError(t *testing.T) { cause := NewRuntimeError("this is inner error") err := NewRuntimeErrorWithMessageCause("", cause) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 9, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError", line) // line = lines[1] assert.Equal(t, " ---> RuntimeError: this is inner error", line) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NormalError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:117")) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[4] assert.Equal(t, " --- End of inner error stack trace ---", line) // line = lines[5] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_EmptyMessage_NormalError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:118")) // line = lines[6] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[7] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[8] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestRuntimeError_Error_NormalMessage_NilError(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", nil) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 5, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: this is error message", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NilError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:153")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[3] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[4] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestRuntimeError_Error_NormalMessage_NormalError(t *testing.T) { cause := NewRuntimeError("this is inner error") err := NewRuntimeErrorWithMessageCause("this is error message", cause) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 9, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: this is error message", line) // line = lines[1] assert.Equal(t, " ---> RuntimeError: this is inner error", line) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NormalError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:175")) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[4] assert.Equal(t, " --- End of inner error stack trace ---", line) // line = lines[5] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_NormalMessage_NormalError() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:176")) // line = lines[6] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[7] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[8] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestRuntimeError_Error_NilStackTrace(t *testing.T) { cause := NewRuntimeError("this is inner error") cause.(*runtimeError).stackTrace = nil err := NewRuntimeErrorWithMessageCause("this is error message", cause) err.(*runtimeError).stackTrace = nil lines := strings.Split(err.Error(), newLine) assert.Equal(t, 5, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: this is error message", line) // line = lines[1] assert.Equal(t, " ---> RuntimeError: this is inner error", line) // line = lines[2] assert.Equal(t, " --- End of inner error stack trace ---", line) // line = lines[3] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[4] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestRuntimeError_Error_MainGoid(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", nil) err.(*runtimeError).goid = 1 lines := strings.Split(err.Error(), newLine) assert.Equal(t, 3, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: this is error message", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_MainGoid() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:235")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) } func TestRuntimeError_Error_ZeroGopc(t *testing.T) { err := NewRuntimeErrorWithMessageCause("this is error message", nil) err.(*runtimeError).gopc = 0 lines := strings.Split(err.Error(), newLine) assert.Equal(t, 3, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: this is error message", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestRuntimeError_Error_ZeroGopc() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:252")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) } func TestArgumentNilError_Goid(t *testing.T) { goid := Goid() err := NewArgumentNilError("number", nil) assert.Equal(t, goid, err.Goid()) task := GoWait(func(token CancelToken) { assert.Equal(t, goid, err.Goid()) assert.NotEqual(t, Goid(), err.Goid()) }) task.Get() } func TestArgumentNilError_Gopc(t *testing.T) { gopc := getg().gopc() err := NewArgumentNilError("number", nil) assert.Equal(t, gopc, err.Gopc()) task := GoWait(func(token CancelToken) { assert.Equal(t, gopc, err.Gopc()) assert.NotEqual(t, getg().gopc(), err.Gopc()) }) task.Get() } func TestArgumentNilError_Message(t *testing.T) { err := NewArgumentNilError("", nil) assert.Equal(t, "Value cannot be null.", err.Message()) err2 := NewArgumentNilError("", "Hello") assert.Equal(t, "Hello", err2.Message()) err3 := NewArgumentNilError("number", nil) assert.Equal(t, "Value cannot be null."+newLine+"Parameter name: number.", err3.Message()) err4 := NewArgumentNilError("number", "Hello") assert.Equal(t, "Hello"+newLine+"Parameter name: number.", err4.Message()) } func TestArgumentNilError_StackTrace(t *testing.T) { err := NewArgumentNilError("", nil) stackTrace := err.StackTrace() capturedStackTrace := captureStackTrace(0, 200) for i := 1; i < len(stackTrace); i++ { assert.Equal(t, capturedStackTrace[i], stackTrace[i]) } } func TestArgumentNilError_Panic_Panic(t *testing.T) { defer func() { cause := recover() assert.NotNil(t, cause) err := NewArgumentNilError("a", nil) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 7, len(lines)) // line := lines[0] assert.Equal(t, "ArgumentNilError: Value cannot be null.", line) // line = lines[1] assert.Equal(t, "Parameter name: a.", line) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Panic_Panic.")) assert.True(t, strings.HasSuffix(line, "error_test.go:337")) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Panic_Panic()")) assert.True(t, strings.HasSuffix(line, "error_test.go:341")) }() defer func() { if cause := recover(); cause != nil { panic(cause) } }() var a any _ = a.(string) } func TestArgumentNilError_Cause(t *testing.T) { err := NewArgumentNilError("", nil) assert.Nil(t, err.Cause()) err2 := NewArgumentNilError("", errors.New("error")) assert.Nil(t, err2.Cause()) err3 := NewArgumentNilError("", &person{Id: 1, Name: "Tim"}) assert.Nil(t, err3.Cause()) err4 := NewArgumentNilError("", err) assert.Same(t, err, err4.Cause()) } func TestArgumentNilError_Error(t *testing.T) { cause := NewRuntimeError("this is inner error") err := NewArgumentNilError("number", cause) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 10, len(lines)) // line := lines[0] assert.Equal(t, "ArgumentNilError: Value cannot be null.", line) // line = lines[1] assert.Equal(t, "Parameter name: number.", line) // line = lines[2] assert.Equal(t, " ---> RuntimeError: this is inner error", line) // line = lines[3] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Error() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:359")) // line = lines[4] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[5] assert.Equal(t, " --- End of inner error stack trace ---", line) // line = lines[6] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestArgumentNilError_Error() in ")) assert.True(t, strings.HasSuffix(line, "error_test.go:360")) // line = lines[7] assert.True(t, strings.HasPrefix(line, " at testing.tRunner() in ")) // line = lines[8] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[9] assert.True(t, strings.HasPrefix(line, " created by testing.(*T).Run() in ")) } func TestArgumentNilError_ParamName(t *testing.T) { err := NewArgumentNilError("", nil) assert.Equal(t, "", err.ParamName()) err2 := NewArgumentNilError("number", nil) assert.Equal(t, "number", err2.ParamName()) } type ArgumentNilError struct { goid uint64 gopc uintptr message string stackTrace []uintptr cause RuntimeError paramName string } func (ae *ArgumentNilError) Goid() uint64 { return ae.goid } func (ae *ArgumentNilError) Gopc() uintptr { return ae.gopc } func (ae *ArgumentNilError) Message() string { builder := &strings.Builder{} if len(ae.message) == 0 { builder.WriteString("Value cannot be null.") } else { builder.WriteString(ae.message) } if len(ae.paramName) != 0 { builder.WriteString(newLine) builder.WriteString("Parameter name: ") builder.WriteString(ae.paramName) builder.WriteString(".") } return builder.String() } func (ae *ArgumentNilError) StackTrace() []uintptr { return ae.stackTrace } func (ae *ArgumentNilError) Cause() RuntimeError { return ae.cause } func (ae *ArgumentNilError) Error() string { return runtimeErrorError(ae) } func (ae *ArgumentNilError) ParamName() string { return ae.paramName } func NewArgumentNilError(paramName string, cause any) *ArgumentNilError { goid, gopc, msg, stackTrace, innerErr := runtimeErrorNew(cause) return &ArgumentNilError{goid: goid, gopc: gopc, message: msg, paramName: paramName, stackTrace: stackTrace, cause: innerErr} } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/future_task.go��������������������������������������������������0000664�0000000�0000000�00000005717�15172351712�0022275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "fmt" "sync" "sync/atomic" "time" ) type taskState = int32 const ( taskStateNew taskState = iota taskStateRunning taskStateCompleted taskStateCanceled taskStateFailed ) type futureTask[TResult any] struct { await sync.WaitGroup state taskState callable FutureCallable[TResult] result TResult error RuntimeError } func (task *futureTask[TResult]) IsDone() bool { state := atomic.LoadInt32(&task.state) return state == taskStateCompleted || state == taskStateCanceled || state == taskStateFailed } func (task *futureTask[TResult]) IsCanceled() bool { return atomic.LoadInt32(&task.state) == taskStateCanceled } func (task *futureTask[TResult]) IsFailed() bool { return atomic.LoadInt32(&task.state) == taskStateFailed } func (task *futureTask[TResult]) Complete(result TResult) { if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateCompleted) || atomic.CompareAndSwapInt32(&task.state, taskStateRunning, taskStateCompleted) { task.result = result task.await.Done() } } func (task *futureTask[TResult]) Cancel() { if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateCanceled) || atomic.CompareAndSwapInt32(&task.state, taskStateRunning, taskStateCanceled) { task.error = NewRuntimeError("Task was canceled.") task.await.Done() } } func (task *futureTask[TResult]) Fail(error any) { if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateFailed) || atomic.CompareAndSwapInt32(&task.state, taskStateRunning, taskStateFailed) { runtimeErr, isRuntimeErr := error.(RuntimeError) if !isRuntimeErr { runtimeErr = NewRuntimeError(error) } task.error = runtimeErr task.await.Done() } } func (task *futureTask[TResult]) Get() TResult { task.await.Wait() if atomic.LoadInt32(&task.state) == taskStateCompleted { return task.result } panic(task.error) } func (task *futureTask[TResult]) GetWithTimeout(timeout time.Duration) TResult { waitChan := make(chan struct{}) go func() { task.await.Wait() close(waitChan) }() timer := time.NewTimer(timeout) defer timer.Stop() select { case <-waitChan: if atomic.LoadInt32(&task.state) == taskStateCompleted { return task.result } panic(task.error) case <-timer.C: task.timeout(timeout) task.await.Wait() if atomic.LoadInt32(&task.state) == taskStateCompleted { return task.result } panic(task.error) } } func (task *futureTask[TResult]) Run() { if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateRunning) { defer func() { if cause := recover(); cause != nil { task.Fail(cause) } }() result := task.callable(task) task.Complete(result) } } func (task *futureTask[TResult]) timeout(timeout time.Duration) { if atomic.CompareAndSwapInt32(&task.state, taskStateNew, taskStateCanceled) || atomic.CompareAndSwapInt32(&task.state, taskStateRunning, taskStateCanceled) { task.error = NewRuntimeError(fmt.Sprintf("Task execution timeout after %v.", timeout)) task.await.Done() } } �������������������������������������������������golang-github-timandy-routine-1.1.6/future_task_test.go���������������������������������������������0000664�0000000�0000000�00000040124�15172351712�0023323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "strings" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" ) func TestFutureTask_IsDone(t *testing.T) { task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) assert.False(t, task.IsDone()) task.Complete(nil) assert.True(t, task.IsDone()) // task2 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) assert.False(t, task2.IsDone()) task2.Cancel() assert.True(t, task2.IsDone()) // task3 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) assert.False(t, task3.IsDone()) task3.Fail(nil) assert.True(t, task3.IsDone()) // task4 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) assert.False(t, task4.IsDone()) task4.(*futureTask[*int]).state = taskStateRunning assert.False(t, task4.IsDone()) } func TestFutureTask_IsCanceled(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) assert.False(t, task.IsCanceled()) task.Cancel() assert.True(t, task.IsCanceled()) } func TestFutureTask_IsFailed(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) assert.False(t, task.IsFailed()) task.Fail(nil) assert.True(t, task.IsFailed()) } func TestFutureTask_Complete_AfterCancel(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task.Cancel() }() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsCanceled()) // task.Complete(2) assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsCanceled()) } func TestFutureTask_Complete_AfterComplete(t *testing.T) { task := NewFutureTask(func(task FutureTask[int]) int { return 1 }) task.Run() assert.Equal(t, 1, task.Get()) task.Complete(2) assert.Equal(t, 1, task.Get()) // run := false task2 := NewFutureTask(func(task FutureTask[int]) int { run = true return 1 }) task2.Complete(2) task2.Run() assert.Equal(t, 2, task2.Get()) assert.False(t, run) } func TestFutureTask_Complete_Common(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task.Complete(1) }() assert.Equal(t, 1, task.Get()) //complete again won't change the result go func() { task.Complete(2) }() assert.Equal(t, 1, task.Get()) } func TestFutureTask_Cancel_AfterComplete(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task.Complete(1) }() assert.Equal(t, 1, task.Get()) task.Cancel() assert.False(t, task.IsCanceled()) assert.Equal(t, 1, task.Get()) } func TestFutureTask_Cancel_Common(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task.Cancel() }() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsCanceled()) assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) } func TestFutureTask_Cancel_RuntimeError(t *testing.T) { task3 := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task3.Cancel() }() assert.Panics(t, func() { task3.Get() }) assert.True(t, task3.IsCanceled()) assert.Equal(t, "Task was canceled.", task3.(*futureTask[int]).error.Message()) assert.Nil(t, task3.(*futureTask[int]).error.Cause()) } func TestFutureTask_Fail_AfterComplete(t *testing.T) { task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { task.Complete(1) }() assert.Equal(t, 1, task.Get()) task.Fail(1) assert.False(t, task.IsFailed()) assert.Equal(t, 1, task.Get()) } func TestFutureTask_Fail_Common(t *testing.T) { defer func() { if cause := recover(); cause != nil { err := cause.(RuntimeError) assert.NotNil(t, err) assert.Equal(t, "1", err.Message()) lines := strings.Split(err.Error(), newLine) // line := lines[0] assert.Equal(t, "RuntimeError: 1", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_Common.")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:169")) } }() // task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { defer func() { if cause := recover(); cause != nil { task.Fail(cause) } }() panic(1) }() task.Get() assert.Fail(t, "should not be here") } func TestFutureTask_Fail_RuntimeError(t *testing.T) { defer func() { if cause := recover(); cause != nil { err := cause.(RuntimeError) assert.NotNil(t, err) assert.Equal(t, "1", err.Message()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 4, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: 1", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_RuntimeError.")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:207")) // line = lines[2] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[3] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Fail_RuntimeError()")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:201")) } }() // task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { defer func() { if cause := recover(); cause != nil { task.Fail(NewRuntimeError(cause)) } }() panic(1) }() task.Get() assert.Fail(t, "should not be here") } func TestFutureTask_Get_Nil(t *testing.T) { run := false task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) go func() { time.Sleep(100 * time.Millisecond) run = true task.Complete(nil) }() assert.Nil(t, task.Get()) assert.True(t, run) } func TestFutureTask_Get_Common(t *testing.T) { run := false task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { time.Sleep(100 * time.Millisecond) run = true task.Complete(1) }() assert.Equal(t, 1, task.Get()) assert.True(t, run) } func TestFutureTask_GetWithTimeout_Complete(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // run := false task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { defer wg.Done() // if task.IsCanceled() { return } run = true task.Complete(1) }() assert.Equal(t, 1, task.GetWithTimeout(100*time.Millisecond)) assert.True(t, run) // wg.Wait() } func TestFutureTask_GetWithTimeout_Fail(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // run := false task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) go func() { defer wg.Done() // if task.IsCanceled() { return } run = true task.Fail(1) }() assert.Panics(t, func() { task.GetWithTimeout(100 * time.Millisecond) }) assert.True(t, run) // assert.True(t, task.IsFailed()) assert.Equal(t, "1", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) // wg.Wait() } func TestFutureTask_GetWithTimeout_Timeout(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // run := false task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) go func() { defer wg.Done() // time.Sleep(100 * time.Millisecond) if task.IsCanceled() { return } run = true task.Complete(nil) }() assert.Panics(t, func() { task.GetWithTimeout(1 * time.Millisecond) }) assert.False(t, run) // assert.True(t, task.IsCanceled()) assert.Equal(t, "Task execution timeout after 1ms.", task.(*futureTask[*int]).error.Message()) assert.Nil(t, task.(*futureTask[*int]).error.Cause()) // wg.Wait() } func TestFutureTask_Run_AfterCancel(t *testing.T) { run := false task := NewFutureTask(func(task FutureTask[*int]) *int { run = true return nil }) task.Cancel() task.Run() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsCanceled()) assert.False(t, run) } func TestFutureTask_Run_AfterFail(t *testing.T) { run := false task := NewFutureTask(func(task FutureTask[*int]) *int { run = true return nil }) task.Fail("failed.") task.Run() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsFailed()) assert.False(t, run) } func TestFutureTask_Run_AfterComplete(t *testing.T) { run := false task := NewFutureTask(func(task FutureTask[int]) int { run = true return 0 }) task.Complete(1) task.Run() assert.Equal(t, 1, task.Get()) assert.True(t, task.IsDone()) assert.False(t, run) } func TestFutureTask_Run_AfterRun(t *testing.T) { var run int32 = 0 wg := &sync.WaitGroup{} wg.Add(1) wg2 := &sync.WaitGroup{} wg2.Add(1) task := NewFutureTask(func(task FutureTask[int]) int { atomic.AddInt32(&run, 1) wg.Done() wg2.Wait() return 1 }) go task.Run() wg.Wait() task.Run() wg2.Done() assert.Equal(t, 1, task.Get()) assert.True(t, task.IsDone()) assert.Equal(t, int32(1), atomic.LoadInt32(&run)) } func TestFutureTask_Run_Normal(t *testing.T) { run := false wg := &sync.WaitGroup{} wg.Add(1) task := NewFutureTask(func(task FutureTask[int]) int { run = true return 1 }) go task.Run() assert.Equal(t, 1, task.Get()) assert.True(t, task.IsDone()) assert.True(t, run) } func TestFutureTask_Run_Error(t *testing.T) { run := false wg := &sync.WaitGroup{} wg.Add(1) task := NewFutureTask(func(task FutureTask[int]) int { run = true panic(1) }) go task.Run() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsFailed()) assert.True(t, run) // defer func() { cause := recover() assert.NotNil(t, cause) assert.Implements(t, (*RuntimeError)(nil), cause) err := cause.(RuntimeError) assert.Equal(t, "1", err.Message()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 5, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: 1", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_Error.")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:397")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) assert.True(t, strings.HasSuffix(line, "future_task.go:108")) // line = lines[3] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[4] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_Error()")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:399")) }() task.Get() assert.Fail(t, "should not be here") } func TestFutureTask_Run_RuntimeError(t *testing.T) { run := false wg := &sync.WaitGroup{} wg.Add(1) task := NewFutureTask(func(task FutureTask[int]) int { run = true err := NewRuntimeError(1) panic(err) }) go task.Run() assert.Panics(t, func() { task.Get() }) assert.True(t, task.IsFailed()) assert.True(t, run) // defer func() { cause := recover() assert.NotNil(t, cause) assert.Implements(t, (*RuntimeError)(nil), cause) err := cause.(RuntimeError) assert.Equal(t, "1", err.Message()) lines := strings.Split(err.Error(), newLine) assert.Equal(t, 5, len(lines)) // line := lines[0] assert.Equal(t, "RuntimeError: 1", line) // line = lines[1] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_RuntimeError.")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:443")) // line = lines[2] assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) assert.True(t, strings.HasSuffix(line, "future_task.go:108")) // line = lines[3] assert.Equal(t, " --- End of error stack trace ---", line) // line = lines[4] assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_RuntimeError()")) assert.True(t, strings.HasSuffix(line, "future_task_test.go:446")) }() task.Get() assert.Fail(t, "should not be here") } func TestFutureTask_Routine_Complete(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWaitResult(func(token CancelToken) int { defer wg.Done() // if token.IsCanceled() { panic("canceled") } time.Sleep(1 * time.Millisecond) return 1 }) assert.Equal(t, 1, task.GetWithTimeout(100*time.Millisecond)) // wg.Wait() } func TestFutureTask_Routine_Cancel(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWaitResult(func(token CancelToken) int { defer wg.Done() // token.Cancel() return 1 }) assert.Panics(t, func() { task.GetWithTimeout(100 * time.Millisecond) }) assert.True(t, task.IsCanceled()) assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) // wg.Wait() } func TestFutureTask_Routine_CancelInParent(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) wg2 := &sync.WaitGroup{} wg2.Add(1) // finished := false task := GoWaitResult(func(token CancelToken) int { wg2.Done() defer wg.Done() // for i := 0; i < 10; i++ { time.Sleep(10 * time.Millisecond) if token.IsCanceled() { return 0 } } finished = true return 1 }) wg2.Wait() task.Cancel() // wg.Wait() // assert.False(t, finished) assert.True(t, task.IsCanceled()) assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) } func TestFutureTask_Routine_Fail(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWaitResult(func(token CancelToken) int { defer wg.Done() // if token.IsCanceled() { return 1 } panic("something error") }) assert.Panics(t, func() { task.GetWithTimeout(100 * time.Millisecond) }) assert.True(t, task.IsFailed()) assert.Equal(t, "something error", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) // wg.Wait() } func TestFutureTask_Routine_Timeout(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWaitResult(func(token CancelToken) int { defer wg.Done() // for i := 0; i < 10; i++ { if token.IsCanceled() { panic("canceled") } time.Sleep(10 * time.Millisecond) } return 1 }) assert.Panics(t, func() { task.GetWithTimeout(1 * time.Millisecond) }) assert.True(t, task.IsCanceled()) assert.Equal(t, "Task execution timeout after 1ms.", task.(*futureTask[int]).error.Message()) assert.Nil(t, task.(*futureTask[int]).error.Cause()) // wg.Wait() } func TestFutureTask_Routine_TimeoutThenComplete(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWait(func(token CancelToken) { defer wg.Done() // ft := token.(*futureTask[any]) ft.result = 1 assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateCompleted)) time.Sleep(50 * time.Millisecond) ft.await.Done() }) assert.Equal(t, 1, task.GetWithTimeout(10*time.Millisecond)) assert.Equal(t, 1, task.Get()) // wg.Wait() } func TestFutureTask_Routine_TimeoutThenCancel(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWait(func(token CancelToken) { defer wg.Done() // ft := token.(*futureTask[any]) ft.error = NewRuntimeError("canceled.") assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateCanceled)) time.Sleep(50 * time.Millisecond) ft.await.Done() }) assert.Panics(t, func() { task.GetWithTimeout(10 * time.Millisecond) }) // assert.True(t, task.IsCanceled()) assert.Equal(t, "canceled.", task.(*futureTask[any]).error.Message()) assert.Nil(t, task.(*futureTask[any]).error.Cause()) assert.Panics(t, func() { task.Get() }) // wg.Wait() } func TestFutureTask_Routine_TimeoutThenFail(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) // task := GoWait(func(token CancelToken) { defer wg.Done() // ft := token.(*futureTask[any]) ft.error = NewRuntimeError("failed.") assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateFailed)) time.Sleep(50 * time.Millisecond) ft.await.Done() }) assert.Panics(t, func() { task.GetWithTimeout(10 * time.Millisecond) }) // assert.True(t, task.IsFailed()) assert.Equal(t, "failed.", task.(*futureTask[any]).error.Message()) assert.Nil(t, task.(*futureTask[any]).error.Cause()) assert.Panics(t, func() { task.Get() }) // wg.Wait() } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g.go������������������������������������������������������������0000664�0000000�0000000�00000002541�15172351712�0020157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "fmt" "reflect" "unsafe" ) type g struct { } //go:norace func (g *g) goid() uint64 { return *(*uint64)(add(unsafe.Pointer(g), offsetGoid)) } //go:norace func (g *g) gopc() uintptr { return *(*uintptr)(add(unsafe.Pointer(g), offsetGopc)) } //go:norace func (g *g) getPanicOnFault() bool { return *(*bool)(add(unsafe.Pointer(g), offsetPaniconfault)) } //go:norace func (g *g) setPanicOnFault(new bool) (old bool) { panicOnFault := (*bool)(add(unsafe.Pointer(g), offsetPaniconfault)) old = *panicOnFault *panicOnFault = new return old } //go:norace func (g *g) getLabels() unsafe.Pointer { return *(*unsafe.Pointer)(add(unsafe.Pointer(g), offsetLabels)) } //go:norace func (g *g) setLabels(labels unsafe.Pointer) { *(*unsafe.Pointer)(add(unsafe.Pointer(g), offsetLabels)) = labels } // getg returns current coroutine struct. func getg() *g { gp := getgp() if gp == nil { panic("Failed to get gp from runtime natively.") } return gp } // offset returns the offset of the specified field. func offset(t reflect.Type, f string) uintptr { field, found := t.FieldByName(f) if found { return field.Offset } panic(fmt.Sprintf("No such field '%v' of struct '%v.%v'.", f, t.PkgPath(), t.Name())) } // add pointer addition operation. func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { return unsafe.Pointer(uintptr(p) + x) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/��������������������������������������������������������������0000775�0000000�0000000�00000000000�15172351712�0017626�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_386.s�����������������������������������������������������0000664�0000000�0000000�00000000502�15172351712�0021167�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "go_tls.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-4 get_tls(CX) MOVL g(CX), AX MOVL AX, ret+0(FP) RET ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_amd64.s���������������������������������������������������0000664�0000000�0000000�00000000502�15172351712�0021562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "go_tls.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 get_tls(CX) MOVQ g(CX), AX MOVQ AX, ret+0(FP) RET ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_arm.s�����������������������������������������������������0000664�0000000�0000000�00000000432�15172351712�0021430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-4 MOVW g, R8 MOVW R8, ret+0(FP) RET ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_arm64.s���������������������������������������������������0000664�0000000�0000000�00000000432�15172351712�0021602�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVD g, R8 MOVD R8, ret+0(FP) RET ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_loong64.s�������������������������������������������������0000664�0000000�0000000�00000000500�15172351712�0022135�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build loong64 // +build loong64 #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVV g, R8 MOVV R8, ret+0(FP) RET ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_mips64x.s�������������������������������������������������0000664�0000000�0000000�00000000523�15172351712�0022164�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build mips64 || mips64le // +build mips64 mips64le #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVV g, R8 MOVV R8, ret+0(FP) RET �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_mipsx.s���������������������������������������������������0000664�0000000�0000000�00000000513�15172351712�0022011�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build mips || mipsle // +build mips mipsle #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-4 MOVW g, R8 MOVW R8, ret+0(FP) RET �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_ppc64x.s��������������������������������������������������0000664�0000000�0000000�00000000517�15172351712�0022001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build ppc64 || ppc64le // +build ppc64 ppc64le #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVD g, R8 MOVD R8, ret+0(FP) RET ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_riscv64.s�������������������������������������������������0000664�0000000�0000000�00000000432�15172351712�0022151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOV g, X10 MOV X10, ret+0(FP) RET ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_s390x.s���������������������������������������������������0000664�0000000�0000000�00000000432�15172351712�0021537�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVD g, R8 MOVD R8, ret+0(FP) RET ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/asm_wasm.s����������������������������������������������������0000664�0000000�0000000�00000000432�15172351712�0021620�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. #include "funcdata.h" #include "go_asm.h" #include "textflag.h" TEXT ·getg(SB), NOSPLIT, $0-8 MOVD g, R8 MOVD R8, ret+0(FP) RET ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/g.go����������������������������������������������������������0000664�0000000�0000000�00000001135�15172351712�0020403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build !routinex package g import ( "reflect" "unsafe" ) // getg returns the pointer to the current runtime.g. // //go:nosplit func getg() unsafe.Pointer // getgp returns the pointer to the current runtime.g. // //go:nosplit //go:linkname getgp runtime.getgp func getgp() unsafe.Pointer { return getg() } // getgt returns the type of runtime.g. // //go:nosplit //go:linkname getgt runtime.getgt func getgt() reflect.Type { return typeByString("runtime.g") } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/g_link.go�����������������������������������������������������0000664�0000000�0000000�00000001123�15172351712�0021415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. //go:build routinex package g import ( "reflect" "unsafe" ) // getg0 returns the value of runtime.g0. // //go:nosplit //go:linkname getg0 runtime.getg0 func getg0() any // getgp returns the pointer to the current runtime.g. // //go:nosplit //go:linkname getgp runtime.getgp func getgp() unsafe.Pointer // getgt returns the type of runtime.g. // //go:nosplit //go:linkname getgt runtime.getgt func getgt() reflect.Type { return reflect.TypeOf(getg0()) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/g_test.go�����������������������������������������������������0000664�0000000�0000000�00000001753�15172351712�0021450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. package g import ( "reflect" "runtime" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestGetgp(t *testing.T) { gp0 := getgp() runtime.GC() assert.NotNil(t, gp0) // runTest(t, func() { gp := getgp() runtime.GC() assert.NotNil(t, gp) assert.NotEqual(t, gp0, gp) }) } func TestGetgt(t *testing.T) { runTest(t, func() { gt := getgt() runtime.GC() assert.Equal(t, "g", gt.Name()) // assert.Greater(t, gt.NumField(), 20) }) } func TestGetg(t *testing.T) { runTest(t, func() { g := packEface(getgt(), getgp()) runtime.GC() stackguard0 := reflect.ValueOf(g).FieldByName("stackguard0") assert.Greater(t, stackguard0.Uint(), uint64(0)) }) } func runTest(t *testing.T, fun func()) { run := false wg := &sync.WaitGroup{} wg.Add(1) go func() { fun() run = true wg.Done() }() wg.Wait() assert.True(t, run) } ���������������������golang-github-timandy-routine-1.1.6/g/go_tls.h������������������������������������������������������0000664�0000000�0000000�00000000556�15172351712�0021274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #ifdef GOARCH_arm #define LR R14 #endif #ifdef GOARCH_amd64 #define get_tls(r) MOVQ TLS, r #define g(r) 0(r)(TLS*1) #endif #ifdef GOARCH_386 #define get_tls(r) MOVL TLS, r #define g(r) 0(r)(TLS*1) #endif ��������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/reflect.go����������������������������������������������������0000664�0000000�0000000�00000005537�15172351712�0021613�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. package g import ( "reflect" "unsafe" ) // eface The empty interface struct. type eface struct { _type unsafe.Pointer data unsafe.Pointer } // iface The interface struct. type iface struct { tab unsafe.Pointer data unsafe.Pointer } // typelinks returns a slice of the sections in each module, // and a slice of *rtype offsets in each module. // The types in each module are sorted by string. // //go:linkname typelinks reflect.typelinks func typelinks() (sections []unsafe.Pointer, offset [][]int32) // resolveTypeOff resolves an *rtype offset from a base type. // //go:linkname resolveTypeOff reflect.resolveTypeOff func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer // isNil returns the data field of eface value is nil or not. // //go:linkname isNil routine.isNil func isNil(i any) bool { return (*eface)(unsafe.Pointer(&i)).data == nil } // packEface returns an empty interface representing a value of the specified type, // using p as the pointer to the data. // //go:linkname packEface routine.packEface func packEface(typ reflect.Type, p unsafe.Pointer) (i any) { t := (*iface)(unsafe.Pointer(&typ)) e := (*eface)(unsafe.Pointer(&i)) e._type = t.data e.data = p return } // typeByString returns the type whose 'String' property equals to the given string, // or nil if not found. // //go:linkname typeByString routine.typeByString func typeByString(str string) reflect.Type { // The s is search target s := str if len(str) == 0 || str[0] != '*' { s = "*" + s } // The typ is a struct iface{tab(ptr->reflect.Type), data(ptr->rtype)} typ := reflect.TypeOf(0) face := (*iface)(unsafe.Pointer(&typ)) // Find the specified target through binary search algorithm sections, offset := typelinks() for offsI, offs := range offset { section := sections[offsI] // We are looking for the first index i where the string becomes >= s. // This is a copy of sort.Search, with f(h) replaced by (*typ[h].String() >= s). i, j := 0, len(offs) for i < j { h := int(uint(i+j) >> 1) // avoid overflow when computing h // i ≤ h < j face.data = resolveTypeOff(section, offs[h]) if !(typ.String() >= s) { //nolint:staticcheck i = h + 1 // preserves f(i-1) == false } else { j = h // preserves f(j) == true } } // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. // Having found the first, linear scan forward to find the last. // We could do a second binary search, but the caller is going // to do a linear scan anyway. if i < len(offs) { face.data = resolveTypeOff(section, offs[i]) if typ.Kind() == reflect.Ptr { if typ.String() == str { return typ } elem := typ.Elem() if elem.String() == str { return elem } } } } return nil } �����������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g/reflect_test.go�����������������������������������������������0000664�0000000�0000000�00000002603�15172351712�0022641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// Copyright 2021-2025 TimAndy. All rights reserved. // Licensed under the Apache-2.0 license that can be found in the LICENSE file. package g import ( "fmt" "reflect" "strings" "testing" "unsafe" "github.com/stretchr/testify/assert" ) func TestIsNil(t *testing.T) { var value fmt.Stringer assert.True(t, value == nil) assert.True(t, isNil(value)) // value = (*strings.Builder)(nil) //nolint:staticcheck assert.True(t, value != nil) //nolint:staticcheck assert.True(t, isNil(value)) // value = &strings.Builder{} //nolint:staticcheck assert.True(t, value != nil) //nolint:staticcheck assert.True(t, !isNil(value)) } func TestPackEface(t *testing.T) { value := 1 valueInterface := packEface(reflect.TypeOf(0), unsafe.Pointer(&value)) assert.Equal(t, value, valueInterface) // value = 2 assert.Equal(t, value, valueInterface) } func TestTypeByString(t *testing.T) { gt := typeByString("runtime.g") assert.NotNil(t, gt) assert.Equal(t, "runtime.g", gt.String()) fGoid, ok := gt.FieldByName("goid") assert.True(t, ok) assert.Greater(t, int(fGoid.Offset), 0) // gt2 := typeByString("*runtime.g") assert.NotNil(t, gt2) assert.Equal(t, "*runtime.g", gt2.String()) fGoid2, ok2 := gt2.Elem().FieldByName("goid") assert.True(t, ok2) assert.Greater(t, int(fGoid2.Offset), 0) assert.Equal(t, fGoid.Offset, fGoid2.Offset) // assert.Nil(t, typeByString("runtime.Pointer")) } �����������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g_test.go�������������������������������������������������������0000664�0000000�0000000�00000006706�15172351712�0021225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "bytes" "fmt" "reflect" "runtime" "strconv" "testing" "unsafe" "github.com/stretchr/testify/assert" ) var goroutineSpace = []byte("goroutine ") func TestG_Goid(t *testing.T) { runTest(t, func() { gp := getg() runtime.GC() assert.Equal(t, curGoroutineID(), gp.goid()) }) } func TestG_Gopc(t *testing.T) { runTest(t, func() { gp := getg() runtime.GC() assert.Greater(t, int64(gp.gopc()), int64(0)) }) } func TestG_PanicOnFault(t *testing.T) { runTest(t, func() { gp := getg() runtime.GC() //read-1 assert.False(t, setPanicOnFault(false)) assert.False(t, gp.getPanicOnFault()) //read-2 setPanicOnFault(true) assert.True(t, gp.getPanicOnFault()) //write-1 gp.setPanicOnFault(false) assert.False(t, setPanicOnFault(false)) //write-2 gp.setPanicOnFault(true) assert.True(t, setPanicOnFault(true)) //write-read-1 gp.setPanicOnFault(false) assert.False(t, gp.getPanicOnFault()) //write-read-2 gp.setPanicOnFault(true) assert.True(t, gp.getPanicOnFault()) //restore gp.setPanicOnFault(false) }) } func TestG_ProfLabel(t *testing.T) { runTest(t, func() { ptr := unsafe.Pointer(&struct{}{}) null := unsafe.Pointer(nil) assert.NotEqual(t, ptr, null) // gp := getg() runtime.GC() //read-1 assert.Equal(t, null, getProfLabel()) assert.Equal(t, null, gp.getLabels()) //read-2 setProfLabel(ptr) assert.Equal(t, ptr, gp.getLabels()) //write-1 gp.setLabels(nil) assert.Equal(t, null, getProfLabel()) //write-2 gp.setLabels(ptr) assert.Equal(t, ptr, getProfLabel()) //write-read-1 gp.setLabels(nil) assert.Equal(t, null, gp.getLabels()) //write-read-2 gp.setLabels(ptr) assert.Equal(t, ptr, gp.getLabels()) //restore gp.setLabels(null) }) } func TestOffset(t *testing.T) { runTest(t, func() { assert.Panics(t, func() { gt := reflect.TypeOf(0) offset(gt, "hello") }) assert.PanicsWithValue(t, "No such field 'hello' of struct 'runtime.g'.", func() { gt := getgt() offset(gt, "hello") }) }) } // curGoroutineID parse the current g's goid from caller stack. func curGoroutineID() uint64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] // Parse the 4707 out of "goroutine 4707 [" b = bytes.TrimPrefix(b, goroutineSpace) i := bytes.IndexByte(b, ' ') if i < 0 { panic(fmt.Sprintf("No space found in %q", b)) } b = b[:i] n, err := strconv.ParseUint(string(b), 10, 64) if err != nil { panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err)) } return n } // setPanicOnFault controls the runtime's behavior when a program faults at an unexpected (non-nil) address. // //go:linkname setPanicOnFault runtime/debug.setPanicOnFault func setPanicOnFault(new bool) (old bool) // getProfLabel get current g's labels which will be inherited by new goroutine. // //go:linkname getProfLabel runtime/pprof.runtime_getProfLabel func getProfLabel() unsafe.Pointer // setProfLabel set current g's labels which will be inherited by new goroutine. // //go:linkname setProfLabel runtime/pprof.runtime_setProfLabel func setProfLabel(labels unsafe.Pointer) //=== // BenchmarkGohack-8 258425366 4.808 ns/op 0 B/op 0 allocs/op func BenchmarkGohack(b *testing.B) { _ = getg() b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { gp := getg() _ = gp.goid() _ = gp.gopc() _ = gp.getLabels() _ = gp.getPanicOnFault() gp.setLabels(nil) gp.setPanicOnFault(false) } } ����������������������������������������������������������golang-github-timandy-routine-1.1.6/g_x.go����������������������������������������������������������0000664�0000000�0000000�00000000566�15172351712�0020513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build !routinex package routine const routinexEnabled = false var ( offsetGoid uintptr offsetPaniconfault uintptr offsetGopc uintptr offsetLabels uintptr ) func init() { gt := getgt() offsetGoid = offset(gt, "goid") offsetPaniconfault = offset(gt, "paniconfault") offsetGopc = offset(gt, "gopc") offsetLabels = offset(gt, "labels") } ������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/g_x_link.go�����������������������������������������������������0000664�0000000�0000000�00000000701�15172351712�0021517�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build routinex package routine const routinexEnabled = true var ( offsetGoid uintptr offsetPaniconfault uintptr offsetGopc uintptr offsetLabels uintptr offsetThreadLocals uintptr ) func init() { gt := getgt() offsetGoid = offset(gt, "goid") offsetPaniconfault = offset(gt, "paniconfault") offsetGopc = offset(gt, "gopc") offsetLabels = offset(gt, "labels") offsetThreadLocals = offset(gt, "threadLocals") } ���������������������������������������������������������������golang-github-timandy-routine-1.1.6/go.mod����������������������������������������������������������0000664�0000000�0000000�00000000353�15172351712�0020507�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������module github.com/timandy/routine go 1.18 require github.com/stretchr/testify v1.10.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/go.sum����������������������������������������������������������0000664�0000000�0000000�00000001563�15172351712�0020540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ���������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/pprof_label_go118.go��������������������������������������������0000664�0000000�0000000�00000000105�15172351712�0023127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build !go1.24 package routine type labelMap map[string]string �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/pprof_label_go124.go��������������������������������������������0000664�0000000�0000000�00000000070�15172351712�0023125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build go1.24 package routine type labelMap []any ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/pprof_label_test.go���������������������������������������������0000664�0000000�0000000�00000000541�15172351712�0023253�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestLabelMap_Nil(t *testing.T) { var labels labelMap assert.Nil(t, labels) // labels = labelMap{} assert.NotNil(t, labels) } func TestLabelMap_Empty(t *testing.T) { var labels labelMap assert.Empty(t, labels) // labels = labelMap{} assert.Empty(t, labels) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/reflect.go������������������������������������������������������0000664�0000000�0000000�00000001134�15172351712�0021352�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "reflect" "unsafe" ) // isNil returns the data field of eface value is nil or not. // //go:linkname isNil routine.isNil func isNil(i any) bool // packEface returns an empty interface representing a value of the specified type, // using p as the pointer to the data. // //go:linkname packEface routine.packEface func packEface(typ reflect.Type, p unsafe.Pointer) (i any) // typeByString returns the type whose 'String' property equals to the given string, // or nil if not found. // //go:linkname typeByString routine.typeByString func typeByString(str string) reflect.Type ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/reflect_test.go�������������������������������������������������0000664�0000000�0000000�00000002403�15172351712�0022411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "fmt" "reflect" "strings" "testing" "unsafe" "github.com/stretchr/testify/assert" ) func TestIsNil(t *testing.T) { var value fmt.Stringer assert.True(t, value == nil) assert.True(t, isNil(value)) // value = (*strings.Builder)(nil) //nolint:staticcheck assert.True(t, value != nil) //nolint:staticcheck assert.True(t, isNil(value)) // value = &strings.Builder{} //nolint:staticcheck assert.True(t, value != nil) //nolint:staticcheck assert.True(t, !isNil(value)) } func TestPackEface(t *testing.T) { value := 1 valueInterface := packEface(reflect.TypeOf(0), unsafe.Pointer(&value)) assert.Equal(t, value, valueInterface) // value = 2 assert.Equal(t, value, valueInterface) } func TestTypeByString(t *testing.T) { gt := typeByString("runtime.g") assert.NotNil(t, gt) assert.Equal(t, "runtime.g", gt.String()) fGoid, ok := gt.FieldByName("goid") assert.True(t, ok) assert.Greater(t, int(fGoid.Offset), 0) // gt2 := typeByString("*runtime.g") assert.NotNil(t, gt2) assert.Equal(t, "*runtime.g", gt2.String()) fGoid2, ok2 := gt2.Elem().FieldByName("goid") assert.True(t, ok2) assert.Greater(t, int(fGoid2.Offset), 0) assert.Equal(t, fGoid.Offset, fGoid2.Offset) // assert.Nil(t, typeByString("runtime.Pointer")) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/routine.go������������������������������������������������������0000664�0000000�0000000�00000002265�15172351712�0021421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import "fmt" type inheritedTask struct { context *threadLocalMap function Runnable } //go:norace func (it inheritedTask) run(task FutureTask[any]) any { // catch defer func() { if cause := recover(); cause != nil { task.Fail(cause) if err := task.(*futureTask[any]).error; err != nil { fmt.Println(err.Error()) } } }() // restore defer restoreInheritedMap(it.context)() // exec it.function() return nil } type inheritedWaitTask struct { context *threadLocalMap function CancelRunnable } //go:norace func (iwt inheritedWaitTask) run(task FutureTask[any]) any { // catch defer func() { if cause := recover(); cause != nil { task.Fail(cause) } }() // restore defer restoreInheritedMap(iwt.context)() // exec iwt.function(task) return nil } type inheritedWaitResultTask[TResult any] struct { context *threadLocalMap function CancelCallable[TResult] } //go:norace func (iwrt inheritedWaitResultTask[TResult]) run(task FutureTask[TResult]) TResult { // catch defer func() { if cause := recover(); cause != nil { task.Fail(cause) } }() // restore defer restoreInheritedMap(iwrt.context)() // exec return iwrt.function(task) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/routine_test.go�������������������������������������������������0000664�0000000�0000000�00000006722�15172351712�0022462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestInheritedTask(t *testing.T) { tls := NewInheritableThreadLocal[string]() it := inheritedTask{context: nil, function: func() { assert.Equal(t, "", tls.Get()) }} task := NewFutureTask(it.run) go task.Run() assert.Nil(t, task.Get()) assert.True(t, task.IsDone()) // it2 := inheritedTask{context: nil, function: func() { assert.Equal(t, "", tls.Get()) }} task2 := NewFutureTask(it2.run) go func() { tls.Set("hello") task2.Run() }() assert.Nil(t, task2.Get()) assert.True(t, task2.IsDone()) // tls.Set("world") it3 := inheritedTask{context: createInheritedMap(), function: func() { assert.Equal(t, "world", tls.Get()) }} task3 := NewFutureTask(it3.run) go task3.Run() assert.Nil(t, task3.Get()) assert.True(t, task3.IsDone()) // it4 := inheritedTask{context: createInheritedMap(), function: func() { assert.Equal(t, "world", tls.Get()) }} task4 := NewFutureTask(it4.run) go func() { tls.Set("hello") task4.Run() }() assert.Nil(t, task4.Get()) assert.True(t, task4.IsDone()) } func TestInheritedWaitTask(t *testing.T) { tls := NewInheritableThreadLocal[string]() it := inheritedWaitTask{context: nil, function: func(token CancelToken) { assert.Equal(t, "", tls.Get()) }} task := NewFutureTask(it.run) go task.Run() assert.Nil(t, task.Get()) assert.True(t, task.IsDone()) // it2 := inheritedWaitTask{context: nil, function: func(token CancelToken) { assert.Equal(t, "", tls.Get()) }} task2 := NewFutureTask(it2.run) go func() { tls.Set("hello") task2.Run() }() assert.Nil(t, task2.Get()) assert.True(t, task2.IsDone()) // tls.Set("world") it3 := inheritedWaitTask{context: createInheritedMap(), function: func(token CancelToken) { assert.Equal(t, "world", tls.Get()) }} task3 := NewFutureTask(it3.run) go task3.Run() assert.Nil(t, task3.Get()) assert.True(t, task3.IsDone()) // it4 := inheritedWaitTask{context: createInheritedMap(), function: func(token CancelToken) { assert.Equal(t, "world", tls.Get()) }} task4 := NewFutureTask(it4.run) go func() { tls.Set("hello") task4.Run() }() assert.Nil(t, task4.Get()) assert.True(t, task4.IsDone()) } func TestInheritedWaitResultTask(t *testing.T) { tls := NewInheritableThreadLocal[string]() it := inheritedWaitResultTask[string]{context: nil, function: func(token CancelToken) string { assert.Equal(t, "", tls.Get()) return tls.Get() }} task := NewFutureTask(it.run) go task.Run() assert.Equal(t, "", task.Get()) assert.True(t, task.IsDone()) // it2 := inheritedWaitResultTask[string]{context: nil, function: func(token CancelToken) string { assert.Equal(t, "", tls.Get()) return tls.Get() }} task2 := NewFutureTask(it2.run) go func() { tls.Set("hello") task2.Run() }() assert.Equal(t, "", task2.Get()) assert.True(t, task2.IsDone()) // tls.Set("world") it3 := inheritedWaitResultTask[string]{context: createInheritedMap(), function: func(token CancelToken) string { assert.Equal(t, "world", tls.Get()) return tls.Get() }} task3 := NewFutureTask(it3.run) go task3.Run() assert.Equal(t, "world", task3.Get()) assert.True(t, task3.IsDone()) // it4 := inheritedWaitResultTask[string]{context: createInheritedMap(), function: func(token CancelToken) string { assert.Equal(t, "world", tls.Get()) return tls.Get() }} task4 := NewFutureTask(it4.run) go func() { tls.Set("hello") task4.Run() }() assert.Equal(t, "world", task4.Get()) assert.True(t, task4.IsDone()) } ����������������������������������������������golang-github-timandy-routine-1.1.6/runtime.go������������������������������������������������������0000664�0000000�0000000�00000000453�15172351712�0021414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "reflect" _ "unsafe" _ "github.com/timandy/routine/g" ) // getgp returns the pointer to the current runtime.g. // //go:linkname getgp runtime.getgp func getgp() *g // getgt returns the type of runtime.g. // //go:linkname getgt runtime.getgt func getgt() reflect.Type ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/runtime_test.go�������������������������������������������������0000664�0000000�0000000�00000004030�15172351712�0022446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "fmt" "os" "reflect" "runtime" "sync" "sync/atomic" "testing" "unsafe" "github.com/stretchr/testify/assert" ) func TestGetgp(t *testing.T) { gp0 := getgp() runtime.GC() assert.NotNil(t, gp0) // runTest(t, func() { gp := getgp() runtime.GC() assert.NotNil(t, gp) assert.NotEqual(t, unsafe.Pointer(gp0), unsafe.Pointer(gp)) }) } func TestGetgt(t *testing.T) { fmt.Println("*** GOOS:", runtime.GOOS, "***") fmt.Println("*** GOARCH:", runtime.GOARCH, "***") if GOARM := os.Getenv("GOARM"); len(GOARM) > 0 { fmt.Println("*** GOARM:", GOARM, "***") } if GOMIPS := os.Getenv("GOMIPS"); len(GOMIPS) > 0 { fmt.Println("*** GOMIPS:", GOMIPS, "***") } // gt := getgt() runtime.GC() assert.Equal(t, "g", gt.Name()) // numField := gt.NumField() // fmt.Println("#numField:", numField) fmt.Println("#offsetGoid:", offsetGoid) fmt.Println("#offsetPaniconfault:", offsetPaniconfault) fmt.Println("#offsetGopc:", offsetGopc) fmt.Println("#offsetLabels:", offsetLabels) // assert.Greater(t, numField, 20) assert.Greater(t, int(offsetGoid), 0) assert.Greater(t, int(offsetPaniconfault), 0) assert.Greater(t, int(offsetGopc), 0) assert.Greater(t, int(offsetLabels), 0) // runTest(t, func() { tt := getgt() runtime.GC() assert.Equal(t, numField, tt.NumField()) assert.Equal(t, offsetGoid, offset(tt, "goid")) assert.Equal(t, offsetPaniconfault, offset(tt, "paniconfault")) assert.Equal(t, offsetGopc, offset(tt, "gopc")) assert.Equal(t, offsetLabels, offset(tt, "labels")) }) } func TestGetg(t *testing.T) { runTest(t, func() { g0 := packEface(getgt(), unsafe.Pointer(getgp())) runtime.GC() stackguard0 := reflect.ValueOf(g0).FieldByName("stackguard0") assert.Greater(t, stackguard0.Uint(), uint64(0)) }) } func runTest(t *testing.T, fun func()) { var count int32 wg := &sync.WaitGroup{} wg.Add(10) for i := 0; i < 10; i++ { go func() { for j := 0; j < 10; j++ { fun() } atomic.AddInt32(&count, 1) wg.Done() }() } wg.Wait() assert.Equal(t, 10, int(count)) } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/stack.go��������������������������������������������������������0000664�0000000�0000000�00000001535�15172351712�0021040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "runtime" "strings" ) const ( runtimePkgPrefix = "runtime." runtimePanic = "panic" ) func captureStackTrace(skip int, depth int) []uintptr { pcs := make([]uintptr, depth) return pcs[:runtime.Callers(skip+2, pcs)] } func showFrame(name string) bool { return strings.IndexByte(name, '.') >= 0 && (!strings.HasPrefix(name, runtimePkgPrefix) || isExportedRuntime(name)) } func skipFrame(name string, skipped bool) bool { return !skipped && isPanicRuntime(name) } func isExportedRuntime(name string) bool { const n = len(runtimePkgPrefix) return len(name) > n && name[:n] == runtimePkgPrefix && 'A' <= name[n] && name[n] <= 'Z' } func isPanicRuntime(name string) bool { const n = len(runtimePkgPrefix) return len(name) > n && name[:n] == runtimePkgPrefix && strings.Contains(strings.ToLower(name[n:]), runtimePanic) } �������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/stack_test.go���������������������������������������������������0000664�0000000�0000000�00000005536�15172351712�0022104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "runtime" "testing" "github.com/stretchr/testify/assert" ) func TestCaptureStackTrace(t *testing.T) { stackTrace := captureStackTrace(0, 10) assert.Greater(t, len(stackTrace), 2) frame, _ := runtime.CallersFrames(stackTrace).Next() assert.Equal(t, "github.com/timandy/routine.TestCaptureStackTrace", frame.Function) assert.Equal(t, 11, frame.Line) // stackTrace2 := captureStackSkip(1) assert.Greater(t, len(stackTrace2), 2) frame2, _ := runtime.CallersFrames(stackTrace2).Next() assert.Equal(t, "github.com/timandy/routine.TestCaptureStackTrace", frame2.Function) assert.Equal(t, 17, frame2.Line) } func TestCaptureStackTrace_Deep(t *testing.T) { stackTrace := captureStackDeep(20) assert.Greater(t, len(stackTrace), 20) frames := runtime.CallersFrames(stackTrace) // frame, more := frames.Next() assert.True(t, more) assert.Equal(t, "github.com/timandy/routine.captureStackDeepRecursive", frame.Function) assert.Equal(t, 93, frame.Line) // frame2, more2 := frames.Next() assert.True(t, more2) assert.Equal(t, "github.com/timandy/routine.captureStackDeepRecursive", frame2.Function) assert.Equal(t, 91, frame2.Line) } func TestCaptureStackTrace_Overflow(t *testing.T) { stackTrace := captureStackDeep(200) assert.Equal(t, 100, len(stackTrace)) } func TestShowFrame(t *testing.T) { assert.False(t, showFrame("make")) assert.True(t, showFrame("strings.equal")) assert.True(t, showFrame("strings.Equal")) assert.False(t, showFrame("runtime.hello")) assert.True(t, showFrame("runtime.Hello")) } func TestSkipFrame(t *testing.T) { assert.False(t, skipFrame("runtime.a", true)) assert.False(t, skipFrame("runtime.gopanic", true)) assert.False(t, skipFrame("runtime.a", false)) assert.True(t, skipFrame("runtime.gopanic", false)) } func TestIsExportedRuntime(t *testing.T) { assert.False(t, isExportedRuntime("")) assert.False(t, isExportedRuntime("runtime.")) assert.False(t, isExportedRuntime("hello_world")) assert.False(t, isExportedRuntime("runtime._")) assert.False(t, isExportedRuntime("runtime.a")) assert.True(t, isExportedRuntime("runtime.Hello")) assert.True(t, isExportedRuntime("runtime.Panic")) } func TestIsPanicRuntime(t *testing.T) { assert.False(t, isPanicRuntime("")) assert.False(t, isPanicRuntime("runtime.")) assert.False(t, isPanicRuntime("hello_world")) assert.False(t, isPanicRuntime("runtime.a")) assert.True(t, isPanicRuntime("runtime.goPanicIndex")) assert.True(t, isPanicRuntime("runtime.gopanic")) assert.True(t, isPanicRuntime("runtime.panicshift")) } func captureStackSkip(skip int) []uintptr { return captureStackTrace(skip, 100) } func captureStackDeep(deep int) []uintptr { return captureStackDeepRecursive(1, deep) } func captureStackDeepRecursive(cur int, deep int) []uintptr { if cur < deep { cur++ return captureStackDeepRecursive(cur, deep) } return captureStackTrace(0, 100) } ������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread.go�������������������������������������������������������0000664�0000000�0000000�00000004022�15172351712�0021174�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build !routinex package routine import ( "runtime" "unsafe" ) const threadMagic = uint64('r')<<48 | uint64('o')<<40 | uint64('u')<<32 | uint64('t')<<24 | uint64('i')<<16 | uint64('n')<<8 | uint64('e') type thread struct { labels labelMap //pprof magic uint64 //mark id uint64 //goid threadLocals *threadLocalMap inheritableThreadLocals *threadLocalMap } // finalize reset thread's memory. func (t *thread) finalize() { t.labels = nil t.magic = 0 t.id = 0 t.threadLocals = nil t.inheritableThreadLocals = nil } // currentThread returns a pointer to the currently executing goroutine's thread struct. // //go:norace //go:nocheckptr func currentThread(create bool) *thread { gp := getg() goid := gp.goid() label := gp.getLabels() //nothing inherited if label == nil { if create { newt := &thread{labels: nil, magic: threadMagic, id: goid} runtime.SetFinalizer(newt, (*thread).finalize) gp.setLabels(unsafe.Pointer(newt)) return newt } return nil } //inherited map then create t, magic, id := extractThread(gp, label) if magic != threadMagic { if create { mp := *(*labelMap)(label) newt := &thread{labels: mp, magic: threadMagic, id: goid} runtime.SetFinalizer(newt, (*thread).finalize) gp.setLabels(unsafe.Pointer(newt)) return newt } return nil } //inherited thread then recreate if id != goid { if create || t.labels != nil { newt := &thread{labels: t.labels, magic: threadMagic, id: goid} runtime.SetFinalizer(newt, (*thread).finalize) gp.setLabels(unsafe.Pointer(newt)) return newt } gp.setLabels(nil) return nil } //all is ok return t } // extractThread extract thread from unsafe.Pointer and catch fault error. // //go:norace //go:nocheckptr func extractThread(gp *g, label unsafe.Pointer) (t *thread, magic uint64, id uint64) { old := gp.setPanicOnFault(true) defer func() { gp.setPanicOnFault(old) recover() //nolint:errcheck }() t = (*thread)(label) return t, t.magic, t.id } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_link.go��������������������������������������������������0000664�0000000�0000000�00000000622�15172351712�0022213�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������//go:build routinex package routine import "unsafe" type thread struct { threadLocals *threadLocalMap inheritableThreadLocals *threadLocalMap } // currentThread returns a pointer to the currently executing goroutine's thread struct. // //go:norace //go:nocheckptr func currentThread(create bool) *thread { gp := getg() return (*thread)(add(unsafe.Pointer(gp), offsetThreadLocals)) } ��������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local.go�������������������������������������������������0000664�0000000�0000000�00000002772�15172351712�0022360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import "sync/atomic" var threadLocalIndex int32 = -1 func nextThreadLocalIndex() int { index := atomic.AddInt32(&threadLocalIndex, 1) if index < 0 { atomic.AddInt32(&threadLocalIndex, -1) panic("too many thread-local indexed variables") } return int(index) } type threadLocal[T any] struct { index int supplier Supplier[T] } func (tls *threadLocal[T]) Get() T { t := currentThread(true) mp := tls.getMap(t) if mp != nil { v := mp.get(tls.index) if v != unset { return entryValue[T](v) } } return tls.setInitialValue(t) } func (tls *threadLocal[T]) Set(value T) { t := currentThread(true) mp := tls.getMap(t) if mp != nil { mp.set(tls.index, entry(value)) } else { tls.createMap(t, value) } } func (tls *threadLocal[T]) Remove() { t := currentThread(false) if t == nil { return } mp := tls.getMap(t) if mp != nil { mp.remove(tls.index) } } //go:norace func (tls *threadLocal[T]) getMap(t *thread) *threadLocalMap { return t.threadLocals } //go:norace func (tls *threadLocal[T]) createMap(t *thread, firstValue T) { mp := &threadLocalMap{} mp.set(tls.index, entry(firstValue)) t.threadLocals = mp } func (tls *threadLocal[T]) setInitialValue(t *thread) T { value := tls.initialValue() mp := tls.getMap(t) if mp != nil { mp.set(tls.index, entry(value)) } else { tls.createMap(t, value) } return value } func (tls *threadLocal[T]) initialValue() T { if tls.supplier == nil { var defaultValue T return defaultValue } return tls.supplier() } ������golang-github-timandy-routine-1.1.6/thread_local_inheritable.go�������������������������������������0000664�0000000�0000000�00000003240�15172351712�0024715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import "sync/atomic" var inheritableThreadLocalIndex int32 = -1 func nextInheritableThreadLocalIndex() int { index := atomic.AddInt32(&inheritableThreadLocalIndex, 1) if index < 0 { atomic.AddInt32(&inheritableThreadLocalIndex, -1) panic("too many inheritable-thread-local indexed variables") } return int(index) } type inheritableThreadLocal[T any] struct { index int supplier Supplier[T] } func (tls *inheritableThreadLocal[T]) Get() T { t := currentThread(true) mp := tls.getMap(t) if mp != nil { v := mp.get(tls.index) if v != unset { return entryValue[T](v) } } return tls.setInitialValue(t) } func (tls *inheritableThreadLocal[T]) Set(value T) { t := currentThread(true) mp := tls.getMap(t) if mp != nil { mp.set(tls.index, entry(value)) } else { tls.createMap(t, value) } } func (tls *inheritableThreadLocal[T]) Remove() { t := currentThread(false) if t == nil { return } mp := tls.getMap(t) if mp != nil { mp.remove(tls.index) } } //go:norace func (tls *inheritableThreadLocal[T]) getMap(t *thread) *threadLocalMap { return t.inheritableThreadLocals } //go:norace func (tls *inheritableThreadLocal[T]) createMap(t *thread, firstValue T) { mp := &threadLocalMap{} mp.set(tls.index, entry(firstValue)) t.inheritableThreadLocals = mp } func (tls *inheritableThreadLocal[T]) setInitialValue(t *thread) T { value := tls.initialValue() mp := tls.getMap(t) if mp != nil { mp.set(tls.index, entry(value)) } else { tls.createMap(t, value) } return value } func (tls *inheritableThreadLocal[T]) initialValue() T { if tls.supplier == nil { var defaultValue T return defaultValue } return tls.supplier() } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local_inheritable_test.go��������������������������������0000664�0000000�0000000�00000015642�15172351712�0025765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "math" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestInheritableThreadLocal_Index(t *testing.T) { tls := NewInheritableThreadLocal[string]() assert.GreaterOrEqual(t, tls.(*inheritableThreadLocal[string]).index, 0) tls2 := NewInheritableThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Greater(t, tls2.(*inheritableThreadLocal[string]).index, tls.(*inheritableThreadLocal[string]).index) } func TestInheritableThreadLocal_NextIndex(t *testing.T) { backup := inheritableThreadLocalIndex defer func() { inheritableThreadLocalIndex = backup }() // inheritableThreadLocalIndex = math.MaxInt32 assert.Panics(t, func() { nextInheritableThreadLocalIndex() }) assert.Equal(t, math.MaxInt32, int(inheritableThreadLocalIndex)) } func TestInheritableThreadLocal_Common(t *testing.T) { tls := NewInheritableThreadLocal[int]() tls2 := NewInheritableThreadLocal[string]() tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(1) tls2.Set("World") assert.Equal(t, 1, tls.Get()) assert.Equal(t, "World", tls2.Get()) // tls.Set(0) tls2.Set("") assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) // tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg := &sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { Go(func() { assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg.Done() }) } wg.Wait() assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) } func TestInheritableThreadLocal_Mixed(t *testing.T) { tls := NewInheritableThreadLocal[int]() tls2 := NewInheritableThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Equal(t, 0, tls.Get()) assert.Equal(t, "Hello", tls2.Get()) // tls.Set(1) tls2.Set("World") assert.Equal(t, 1, tls.Get()) assert.Equal(t, "World", tls2.Get()) // tls.Set(0) tls2.Set("") assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) // tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "Hello", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg := &sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { Go(func() { assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg.Done() }) } wg.Wait() assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) } func TestInheritableThreadLocal_WithInitial(t *testing.T) { src := &person{Id: 1, Name: "Tim"} tls := NewInheritableThreadLocalWithInitial[*person](nil) tls2 := NewInheritableThreadLocalWithInitial[*person](func() *person { var value *person return value }) tls3 := NewInheritableThreadLocalWithInitial[*person](func() *person { return src }) tls4 := NewInheritableThreadLocalWithInitial[person](func() person { return *src }) for i := 0; i < 100; i++ { p := tls.Get() assert.Nil(t, p) // p2 := tls2.Get() assert.Nil(t, p2) // p3 := tls3.Get() assert.Same(t, src, p3) p4 := tls4.Get() assert.NotSame(t, src, &p4) assert.Equal(t, *src, p4) wg := &sync.WaitGroup{} wg.Add(1) Go(func() { assert.Same(t, src, tls3.Get()) p5 := tls4.Get() assert.NotSame(t, src, &p5) assert.Equal(t, *src, p5) // wg.Done() }) wg.Wait() } tls3.Set(nil) tls4.Set(person{}) assert.Nil(t, tls3.Get()) assert.Equal(t, person{}, tls4.Get()) tls3.Remove() tls4.Remove() assert.Same(t, src, tls3.Get()) p6 := tls4.Get() assert.NotSame(t, src, &p6) assert.Equal(t, *src, p6) } func TestInheritableThreadLocal_CrossCoroutine(t *testing.T) { tls := NewInheritableThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) subWait := &sync.WaitGroup{} subWait.Add(2) finishWait := &sync.WaitGroup{} finishWait.Add(2) go func() { subWait.Wait() assert.Equal(t, "", tls.Get()) finishWait.Done() }() Go(func() { subWait.Wait() assert.Equal(t, "Hello", tls.Get()) finishWait.Done() }) tls.Remove() //remove in parent goroutine should not affect child goroutine subWait.Done() //allow sub goroutine run subWait.Done() //allow sub goroutine run finishWait.Wait() //wait sub goroutine done finishWait.Wait() //wait sub goroutine done } func TestInheritableThreadLocal_CreateBatch(t *testing.T) { const count = 128 tlsList := make([]ThreadLocal[int], count) for i := 0; i < count; i++ { value := i tlsList[i] = NewInheritableThreadLocalWithInitial[int](func() int { return value }) } for i := 0; i < count; i++ { assert.Equal(t, i, tlsList[i].Get()) } } func TestInheritableThreadLocal_Copy(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[*person](func() *person { return &person{Id: 1, Name: "Tim"} }) tls2 := NewInheritableThreadLocalWithInitial[person](func() person { return person{Id: 2, Name: "Andy"} }) p1 := tls.Get() assert.Equal(t, 1, p1.Id) assert.Equal(t, "Tim", p1.Name) p2 := tls2.Get() assert.Equal(t, 2, p2.Id) assert.Equal(t, "Andy", p2.Name) // task := GoWait(func(token CancelToken) { p3 := tls.Get() assert.Same(t, p1, p3) assert.Equal(t, 1, p3.Id) assert.Equal(t, "Tim", p1.Name) p4 := tls2.Get() assert.NotSame(t, &p2, &p4) assert.Equal(t, p2, p4) assert.Equal(t, 2, p4.Id) assert.Equal(t, "Andy", p4.Name) // p3.Name = "Tim2" p4.Name = "Andy2" }) task.Get() // p5 := tls.Get() assert.Same(t, p1, p5) assert.Equal(t, 1, p5.Id) assert.Equal(t, "Tim2", p5.Name) p6 := tls2.Get() assert.NotSame(t, &p2, &p6) assert.Equal(t, p2, p6) assert.Equal(t, 2, p6.Id) assert.Equal(t, "Andy", p6.Name) } func TestInheritableThreadLocal_Cloneable(t *testing.T) { tls := NewInheritableThreadLocalWithInitial[*personCloneable](func() *personCloneable { return &personCloneable{Id: 1, Name: "Tim"} }) tls2 := NewInheritableThreadLocalWithInitial[personCloneable](func() personCloneable { return personCloneable{Id: 2, Name: "Andy"} }) p1 := tls.Get() assert.Equal(t, 1, p1.Id) assert.Equal(t, "Tim", p1.Name) p2 := tls2.Get() assert.Equal(t, 2, p2.Id) assert.Equal(t, "Andy", p2.Name) // task := GoWait(func(token CancelToken) { p3 := tls.Get() //p3 is clone from p1 assert.NotSame(t, p1, p3) assert.Equal(t, 1, p3.Id) assert.Equal(t, "Tim", p1.Name) p4 := tls2.Get() assert.NotSame(t, &p2, &p4) assert.Equal(t, p2, p4) assert.Equal(t, 2, p4.Id) assert.Equal(t, "Andy", p4.Name) // p3.Name = "Tim2" p4.Name = "Andy2" }) task.Get() // p5 := tls.Get() assert.Same(t, p1, p5) assert.Equal(t, 1, p5.Id) assert.Equal(t, "Tim", p5.Name) p6 := tls2.Get() assert.NotSame(t, &p2, &p6) assert.Equal(t, p2, p6) assert.Equal(t, 2, p6.Id) assert.Equal(t, "Andy", p6.Name) } ����������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local_map.go���������������������������������������������0000664�0000000�0000000�00000004771�15172351712�0023216�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import _ "unsafe" var unset entry = &object{} type object struct { none bool //nolint:unused } type threadLocalMap struct { table []entry } func (mp *threadLocalMap) get(index int) entry { lookup := mp.table if index < len(lookup) { return lookup[index] } return unset } func (mp *threadLocalMap) set(index int, value entry) { lookup := mp.table if index < len(lookup) { lookup[index] = value return } mp.expandAndSet(index, value) } func (mp *threadLocalMap) remove(index int) { lookup := mp.table if index < len(lookup) { lookup[index] = unset } } func (mp *threadLocalMap) expandAndSet(index int, value entry) { oldArray := mp.table oldCapacity := len(oldArray) newCapacity := index newCapacity |= newCapacity >> 1 newCapacity |= newCapacity >> 2 newCapacity |= newCapacity >> 4 newCapacity |= newCapacity >> 8 newCapacity |= newCapacity >> 16 newCapacity++ newArray := make([]entry, newCapacity) copy(newArray, oldArray) fill(newArray, oldCapacity, newCapacity, unset) newArray[index] = value mp.table = newArray } //go:norace //go:linkname createInheritedMap routine.createInheritedMap func createInheritedMap() *threadLocalMap { parent := currentThread(false) if parent == nil { return nil } parentMap := parent.inheritableThreadLocals if parentMap == nil { return nil } lookup := parentMap.table if lookup == nil { return nil } table := make([]entry, len(lookup)) copy(table, lookup) for i := 0; i < len(table); i++ { if c, ok := entryAssert[Cloneable](table[i]); ok && !isNil(c) { table[i] = entry(c.Clone()) } } return &threadLocalMap{table: table} } //go:norace //go:linkname restoreInheritedMap routine.restoreInheritedMap func restoreInheritedMap(mp *threadLocalMap) func() { t := currentThread(mp != nil) if t == nil { // mp and t are nil return clearThread } threadLocalsBackup := t.threadLocals inheritableThreadLocalsBackup := t.inheritableThreadLocals t.threadLocals = nil t.inheritableThreadLocals = mp return func() { resetThread(t, threadLocalsBackup, inheritableThreadLocalsBackup) } } //go:norace func clearThread() { t := currentThread(false) if t != nil { t.threadLocals = nil t.inheritableThreadLocals = nil } } //go:norace func resetThread(t *thread, threadLocals, inheritableThreadLocals *threadLocalMap) { t.threadLocals = threadLocals t.inheritableThreadLocals = inheritableThreadLocals } func fill[T any](a []T, fromIndex int, toIndex int, val T) { for i := fromIndex; i < toIndex; i++ { a[i] = val } } �������golang-github-timandy-routine-1.1.6/thread_local_map_entry.go���������������������������������������0000664�0000000�0000000�00000000340�15172351712�0024423�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine type entry any func entryValue[T any](e entry) T { if e == nil { var defaultValue T return defaultValue } return e.(T) } func entryAssert[T any](e entry) (T, bool) { v, ok := e.(T) return v, ok } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local_map_entry_test.go����������������������������������0000664�0000000�0000000�00000007467�15172351712�0025503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "testing" "github.com/stretchr/testify/assert" ) func TestEntry_Clone(t *testing.T) { expect := &personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value := entryValue[*personCloneable](e) assert.NotNil(t, value) assert.Equal(t, *expect, *value) // c, ok := entryAssert[Cloneable](e) assert.True(t, ok) assert.Equal(t, *expect, *c.(*personCloneable)) // copied := entry(c.Clone()) value2 := entryValue[*personCloneable](copied) assert.NotNil(t, value2) assert.Equal(t, *expect, *value2) // c3, ok2 := entryAssert[Cloneable](copied) assert.True(t, ok2) assert.Equal(t, *expect, *c3.(*personCloneable)) } //=== func TestEntry_Value_Nil(t *testing.T) { e := entry(nil) value := entryValue[any](e) assert.Nil(t, value) } func TestEntry_Value_NotNil(t *testing.T) { expect := 1 e := entry(expect) value := entryValue[int](e) assert.Equal(t, expect, value) } func TestEntry_Value_Default(t *testing.T) { expect := 0 e := entry(expect) value := entryValue[int](e) assert.Equal(t, expect, value) } func TestEntry_Value_Interface_Nil(t *testing.T) { var expect Cloneable e := entry(expect) value := entryValue[Cloneable](e) assert.Nil(t, value) } func TestEntry_Value_Interface_NotNil(t *testing.T) { var expect Cloneable = &personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value := entryValue[Cloneable](e) assert.Same(t, expect, value) } func TestEntry_Value_Pointer_Nil(t *testing.T) { var expect *personCloneable e := entry(expect) value := entryValue[*personCloneable](e) assert.Nil(t, value) } func TestEntry_Value_Pointer_NotNil(t *testing.T) { expect := &personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value := entryValue[*personCloneable](e) assert.Same(t, expect, value) } func TestEntry_Value_Struct_Default(t *testing.T) { expect := personCloneable{} e := entry(expect) value := entryValue[personCloneable](e) assert.Equal(t, expect, value) } func TestEntry_Value_Struct_NotDefault(t *testing.T) { expect := personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value := entryValue[personCloneable](e) assert.Equal(t, expect, value) } //=== func TestEntry_Assert_Nil(t *testing.T) { e := entry(nil) value, ok := entryAssert[any](e) assert.False(t, ok) assert.Nil(t, value) } func TestEntry_Assert_NotNil(t *testing.T) { expect := 1 e := entry(expect) value, ok := entryAssert[int](e) assert.True(t, ok) assert.Equal(t, expect, value) } func TestEntry_Assert_Default(t *testing.T) { expect := 0 e := entry(expect) value, ok := entryAssert[int](e) assert.True(t, ok) assert.Equal(t, expect, value) } func TestEntry_Assert_Interface_Nil(t *testing.T) { var expect Cloneable e := entry(expect) value, ok := entryAssert[Cloneable](e) assert.False(t, ok) assert.Nil(t, value) } func TestEntry_Assert_Interface_NotNil(t *testing.T) { var expect Cloneable = &personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value, ok := entryAssert[Cloneable](e) assert.True(t, ok) assert.Same(t, expect, value) } func TestEntry_Assert_Pointer_Nil(t *testing.T) { var expect *personCloneable e := entry(expect) value, ok := entryAssert[*personCloneable](e) assert.True(t, ok) assert.Nil(t, value) } func TestEntry_Assert_Pointer_NotNil(t *testing.T) { expect := &personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value, ok := entryAssert[*personCloneable](e) assert.True(t, ok) assert.Same(t, expect, value) } func TestEntry_Assert_Struct_Default(t *testing.T) { expect := personCloneable{} e := entry(expect) value, ok := entryAssert[personCloneable](e) assert.True(t, ok) assert.Equal(t, expect, value) } func TestEntry_Assert_Struct_NotDefault(t *testing.T) { expect := personCloneable{Id: 1, Name: "Hello"} e := entry(expect) value, ok := entryAssert[personCloneable](e) assert.True(t, ok) assert.Equal(t, expect, value) } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local_map_test.go����������������������������������������0000664�0000000�0000000�00000017137�15172351712�0024255�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "math/rand" "sync" "testing" "unsafe" "github.com/stretchr/testify/assert" ) //go:linkname createInheritedMapExport routine.createInheritedMap func createInheritedMapExport() unsafe.Pointer //go:linkname restoreInheritedMapExport routine.restoreInheritedMap func restoreInheritedMapExport(mp unsafe.Pointer) func() func TestObject(t *testing.T) { var value entry = &object{} assert.NotSame(t, unset, value) // var value2 entry = &object{} assert.NotSame(t, value, value2) // var value3 any = unset assert.Same(t, unset, value3) } func TestCreateInheritedMap(t *testing.T) { wg := &sync.WaitGroup{} wg.Add(1) go func() { thd := currentThread(true) assert.NotNil(t, thd) assert.Nil(t, thd.inheritableThreadLocals) thd.inheritableThreadLocals = &threadLocalMap{} assert.Nil(t, thd.inheritableThreadLocals.table) assert.Nil(t, createInheritedMap()) // wg.Done() }() wg.Wait() } func TestCreateInheritedMap_Nil(t *testing.T) { tls := NewInheritableThreadLocal[string]() tls.Set("") srcValue := tls.Get() assert.Equal(t, "", srcValue) assert.True(t, srcValue == "") mp := createInheritedMap() assert.NotNil(t, mp) getValue := entryValue[string](mp.get(tls.(*inheritableThreadLocal[string]).index)) assert.Equal(t, "", getValue) assert.True(t, getValue == "") mp2 := createInheritedMap() assert.NotNil(t, mp2) assert.NotSame(t, mp, mp2) getValue2 := entryValue[string](mp2.get(tls.(*inheritableThreadLocal[string]).index)) assert.Equal(t, "", getValue2) assert.True(t, getValue2 == "") } func TestCreateInheritedMap_Value(t *testing.T) { tls := NewInheritableThreadLocal[uint64]() value := rand.Uint64() tls.Set(value) srcValue := tls.Get() assert.NotSame(t, &value, &srcValue) assert.Equal(t, value, srcValue) mp := createInheritedMap() assert.NotNil(t, mp) getValue := entryValue[uint64](mp.get(tls.(*inheritableThreadLocal[uint64]).index)) assert.NotSame(t, &value, &getValue) assert.Equal(t, value, getValue) mp2 := createInheritedMap() assert.NotNil(t, mp2) assert.NotSame(t, mp, mp2) getValue2 := entryValue[uint64](mp2.get(tls.(*inheritableThreadLocal[uint64]).index)) assert.NotSame(t, &value, &getValue2) assert.Equal(t, value, getValue2) } func TestCreateInheritedMap_Struct(t *testing.T) { tls := NewInheritableThreadLocal[personCloneable]() value := personCloneable{Id: 1, Name: "Hello"} tls.Set(value) srcValue := tls.Get() assert.NotSame(t, &value, &srcValue) assert.Equal(t, value, srcValue) mp := createInheritedMap() assert.NotNil(t, mp) getValue := entryValue[personCloneable](mp.get(tls.(*inheritableThreadLocal[personCloneable]).index)) assert.NotSame(t, &value, &getValue) assert.Equal(t, value, getValue) mp2 := createInheritedMap() assert.NotNil(t, mp2) assert.NotSame(t, mp, mp2) getValue2 := entryValue[personCloneable](mp2.get(tls.(*inheritableThreadLocal[personCloneable]).index)) assert.NotSame(t, &value, &getValue2) assert.Equal(t, value, getValue2) } func TestCreateInheritedMap_Pointer(t *testing.T) { tls := NewInheritableThreadLocal[*person]() value := &person{Id: 1, Name: "Hello"} tls.Set(value) srcValue := tls.Get() assert.Same(t, value, srcValue) assert.Equal(t, *value, *srcValue) mp := createInheritedMap() assert.NotNil(t, mp) getValue := entryValue[*person](mp.get(tls.(*inheritableThreadLocal[*person]).index)) assert.Same(t, value, getValue) assert.Equal(t, *value, *getValue) mp2 := createInheritedMap() assert.NotNil(t, mp2) assert.NotSame(t, mp, mp2) getValue2 := entryValue[*person](mp2.get(tls.(*inheritableThreadLocal[*person]).index)) assert.Same(t, value, getValue2) assert.Equal(t, *value, *getValue2) } func TestCreateInheritedMap_Cloneable(t *testing.T) { tls := NewInheritableThreadLocal[*personCloneable]() value := &personCloneable{Id: 1, Name: "Hello"} tls.Set(value) srcValue := tls.Get() assert.Same(t, value, srcValue) assert.Equal(t, *value, *srcValue) mp := createInheritedMap() assert.NotNil(t, mp) getValue := entryValue[*personCloneable](mp.get(tls.(*inheritableThreadLocal[*personCloneable]).index)) assert.NotSame(t, value, getValue) assert.Equal(t, *value, *getValue) mp2 := createInheritedMap() assert.NotNil(t, mp2) assert.NotSame(t, mp, mp2) getValue2 := entryValue[*personCloneable](mp2.get(tls.(*inheritableThreadLocal[*personCloneable]).index)) assert.NotSame(t, value, getValue2) assert.Equal(t, *value, *getValue2) } func TestRestoreInheritedMap(t *testing.T) { tls := NewInheritableThreadLocal[*personCloneable]() value := &personCloneable{Id: 1, Name: "Hello"} wg := sync.WaitGroup{} wg.Add(2) go func() { mp := createInheritedMap() assert.Nil(t, mp) // go func() { defer func() { if routinexEnabled { assert.NotNil(t, currentThread(false)) } else { assert.Nil(t, currentThread(false)) } assert.Nil(t, tls.Get()) wg.Done() }() defer restoreInheritedMap(mp)() }() wg.Done() }() wg.Wait() // wg2 := sync.WaitGroup{} wg2.Add(2) go func() { mp := createInheritedMap() assert.Nil(t, mp) // go func() { defer func() { assert.NotNil(t, currentThread(false)) assert.Nil(t, tls.Get()) wg2.Done() }() defer restoreInheritedMap(mp)() tls.Set(value) assert.Same(t, value, tls.Get()) }() wg2.Done() }() wg2.Wait() // value2 := &personCloneable{Id: 2, Name: "World"} wg3 := sync.WaitGroup{} wg3.Add(2) go func() { tls.Set(value) assert.Same(t, value, tls.Get()) mp := createInheritedMap() assert.NotNil(t, mp) // go func() { defer func() { assert.NotNil(t, currentThread(false)) assert.Same(t, value2, tls.Get()) wg3.Done() }() tls.Set(value2) assert.Same(t, value2, tls.Get()) defer restoreInheritedMap(mp)() result := tls.Get() assert.NotNil(t, result) assert.NotSame(t, value, result) assert.NotSame(t, value2, result) assert.Equal(t, *value, *result) }() wg3.Done() }() wg3.Wait() } func TestRestoreInheritedMap_Export(t *testing.T) { tls := NewInheritableThreadLocal[*personCloneable]() value := &personCloneable{Id: 1, Name: "Hello"} wg := sync.WaitGroup{} wg.Add(2) go func() { mp := createInheritedMapExport() assert.Nil(t, mp) // go func() { defer func() { if routinexEnabled { assert.NotNil(t, currentThread(false)) } else { assert.Nil(t, currentThread(false)) } assert.Nil(t, tls.Get()) wg.Done() }() defer restoreInheritedMapExport(mp)() }() wg.Done() }() wg.Wait() // wg2 := sync.WaitGroup{} wg2.Add(2) go func() { mp := createInheritedMapExport() assert.Nil(t, mp) // go func() { defer func() { assert.NotNil(t, currentThread(false)) assert.Nil(t, tls.Get()) wg2.Done() }() defer restoreInheritedMapExport(mp)() tls.Set(value) assert.Same(t, value, tls.Get()) }() wg2.Done() }() wg2.Wait() // value2 := &personCloneable{Id: 2, Name: "World"} wg3 := sync.WaitGroup{} wg3.Add(2) go func() { tls.Set(value) assert.Same(t, value, tls.Get()) mp := createInheritedMapExport() assert.NotNil(t, mp) // go func() { defer func() { assert.NotNil(t, currentThread(false)) assert.Same(t, value2, tls.Get()) wg3.Done() }() tls.Set(value2) assert.Same(t, value2, tls.Get()) defer restoreInheritedMapExport(mp)() result := tls.Get() assert.NotNil(t, result) assert.NotSame(t, value, result) assert.NotSame(t, value2, result) assert.Equal(t, *value, *result) }() wg3.Done() }() wg3.Wait() } func TestFill(t *testing.T) { a := make([]entry, 6) fill(a, 4, 5, unset) for i := 0; i < 6; i++ { if i == 4 { assert.True(t, a[i] == unset) } else { assert.Nil(t, a[i]) assert.True(t, a[i] != unset) } } } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_local_test.go��������������������������������������������0000664�0000000�0000000�00000011123�15172351712�0023405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "math" "sync" "testing" "github.com/stretchr/testify/assert" ) func TestThreadLocal_Index(t *testing.T) { tls := NewThreadLocal[string]() assert.GreaterOrEqual(t, tls.(*threadLocal[string]).index, 0) tls2 := NewThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Greater(t, tls2.(*threadLocal[string]).index, tls.(*threadLocal[string]).index) } func TestThreadLocal_NextIndex(t *testing.T) { backup := threadLocalIndex defer func() { threadLocalIndex = backup }() // threadLocalIndex = math.MaxInt32 assert.Panics(t, func() { nextThreadLocalIndex() }) assert.Equal(t, math.MaxInt32, int(threadLocalIndex)) } func TestThreadLocal_Common(t *testing.T) { tls := NewThreadLocal[int]() tls2 := NewThreadLocal[string]() tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(1) tls2.Set("World") assert.Equal(t, 1, tls.Get()) assert.Equal(t, "World", tls2.Get()) // tls.Set(0) tls2.Set("") assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) // tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg := &sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { Go(func() { assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) wg.Done() }) } wg.Wait() assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) } func TestThreadLocal_Mixed(t *testing.T) { tls := NewThreadLocal[int]() tls2 := NewThreadLocalWithInitial[string](func() string { return "Hello" }) assert.Equal(t, 0, tls.Get()) assert.Equal(t, "Hello", tls2.Get()) // tls.Set(1) tls2.Set("World") assert.Equal(t, 1, tls.Get()) assert.Equal(t, "World", tls2.Get()) // tls.Set(0) tls2.Set("") assert.Equal(t, 0, tls.Get()) assert.Equal(t, "", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) // tls.Remove() tls2.Remove() assert.Equal(t, 0, tls.Get()) assert.Equal(t, "Hello", tls2.Get()) // tls.Set(2) tls2.Set("!") assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) wg := &sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { Go(func() { assert.Equal(t, 0, tls.Get()) assert.Equal(t, "Hello", tls2.Get()) wg.Done() }) } wg.Wait() assert.Equal(t, 2, tls.Get()) assert.Equal(t, "!", tls2.Get()) } func TestThreadLocal_WithInitial(t *testing.T) { src := &person{Id: 1, Name: "Tim"} tls := NewThreadLocalWithInitial[*person](nil) tls2 := NewThreadLocalWithInitial[*person](func() *person { var value *person return value }) tls3 := NewThreadLocalWithInitial[*person](func() *person { return src }) tls4 := NewThreadLocalWithInitial[person](func() person { return *src }) for i := 0; i < 100; i++ { p := tls.Get() assert.Nil(t, p) // p2 := tls2.Get() assert.Nil(t, p2) // p3 := tls3.Get() assert.Same(t, src, p3) p4 := tls4.Get() assert.NotSame(t, src, &p4) assert.Equal(t, *src, p4) wg := &sync.WaitGroup{} wg.Add(1) Go(func() { assert.Same(t, src, tls3.Get()) p5 := tls4.Get() assert.NotSame(t, src, &p5) assert.Equal(t, *src, p5) // wg.Done() }) wg.Wait() } tls3.Set(nil) tls4.Set(person{}) assert.Nil(t, tls3.Get()) assert.Equal(t, person{}, tls4.Get()) tls3.Remove() tls4.Remove() assert.Same(t, src, tls3.Get()) p6 := tls4.Get() assert.NotSame(t, src, &p6) assert.Equal(t, *src, p6) } func TestThreadLocal_CrossCoroutine(t *testing.T) { tls := NewThreadLocal[string]() tls.Set("Hello") assert.Equal(t, "Hello", tls.Get()) subWait := &sync.WaitGroup{} subWait.Add(2) finishWait := &sync.WaitGroup{} finishWait.Add(2) go func() { subWait.Wait() assert.Equal(t, "", tls.Get()) finishWait.Done() }() Go(func() { subWait.Wait() assert.Equal(t, "", tls.Get()) finishWait.Done() }) tls.Remove() //remove in parent goroutine should not affect child goroutine subWait.Done() //allow sub goroutine run subWait.Done() //allow sub goroutine run finishWait.Wait() //wait sub goroutine done finishWait.Wait() //wait sub goroutine done } func TestThreadLocal_CreateBatch(t *testing.T) { const count = 128 tlsList := make([]ThreadLocal[int], count) for i := 0; i < count; i++ { value := i tlsList[i] = NewThreadLocalWithInitial[int](func() int { return value }) } for i := 0; i < count; i++ { assert.Equal(t, i, tlsList[i].Get()) } } type person struct { Id int Name string } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������golang-github-timandy-routine-1.1.6/thread_test.go��������������������������������������������������0000664�0000000�0000000�00000006340�15172351712�0022240�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������package routine import ( "bytes" "context" "runtime" "runtime/pprof" "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func TestCurrentThread(t *testing.T) { assert.NotNil(t, currentThread(true)) assert.Same(t, currentThread(true), currentThread(true)) } func TestPProf(t *testing.T) { const concurrency = 10 const loopTimes = 10 tls := NewThreadLocal[any]() 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(100 * time.Millisecond) tls.Set(tmp) assert.Equal(t, tmp, tls.Get()) pprof.Do(context.Background(), pprof.Labels("key", "value"), func(ctx context.Context) { if routinexEnabled { assert.Equal(t, tmp, tls.Get()) } else { assert.Nil(t, currentThread(false)) assert.Nil(t, tls.Get()) } tls.Set("hi") // label, find := pprof.Label(ctx, "key") assert.True(t, find) assert.Equal(t, "value", label) // assert.Equal(t, "hi", tls.Get()) // label2, find2 := pprof.Label(ctx, "key") assert.True(t, find2) assert.Equal(t, "value", label2) }) if routinexEnabled { assert.Equal(t, "hi", tls.Get()) } else { assert.Nil(t, tls.Get()) } } wg.Done() }() } assert.Nil(t, pprof.StartCPUProfile(&bytes.Buffer{})) wg.Wait() pprof.StopCPUProfile() assert.Equal(t, "你好", tls.Get()) } func TestThreadGC(t *testing.T) { const allocSize = 10_000_000 tls := NewThreadLocal[[]byte]() tls2 := NewInheritableThreadLocal[[]byte]() allocWait := &sync.WaitGroup{} allocWait.Add(1) gatherWait := &sync.WaitGroup{} gatherWait.Add(1) gcWait := &sync.WaitGroup{} gcWait.Add(1) //=========Init heapInit, numInit := getMemStats() printMemStats("Init", heapInit, numInit) // task := GoWait(func(token CancelToken) { tls.Set(make([]byte, allocSize)) tls2.Set(make([]byte, allocSize)) go func() { gcWait.Wait() }() task2 := GoWaitResult(func(token CancelToken) int { return 1 }) assert.Equal(t, 1, task2.Get()) allocWait.Done() //alloc ok, release main thread gatherWait.Wait() //wait gather heap info }) //=========Alloc allocWait.Wait() //wait alloc done heapAlloc, numAlloc := getMemStats() printMemStats("Alloc", heapAlloc, numAlloc) assert.Greater(t, heapAlloc, heapInit+allocSize*2*0.9) assert.Greater(t, numAlloc, numInit) //=========GC gatherWait.Done() //gather ok, release sub thread task.Get() //wait sub thread finish time.Sleep(500 * time.Millisecond) heapGC, numGC := getMemStats() printMemStats("AfterGC", heapGC, numGC) gcWait.Done() //=========Summary heapRelease := heapAlloc - heapGC numRelease := numAlloc - numGC printMemStats("Summary", heapRelease, numRelease) assert.Greater(t, int(heapRelease), int(allocSize*2*0.9)) assert.Equal(t, 1, numRelease) } func getMemStats() (uint64, int) { stats := runtime.MemStats{} runtime.GC() runtime.ReadMemStats(&stats) return stats.HeapAlloc, runtime.NumGoroutine() } func printMemStats(section string, heapAlloc uint64, numGoroutine int) { //fmt.Printf("%v\n", section) //fmt.Printf("HeapAlloc = %v\n", heapAlloc) //fmt.Printf("NumGoroutine = %v\n", numGoroutine) //fmt.Printf("===\n") } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������