├── go.mod ├── sabotage.jpg ├── pinp_test.go ├── gid.go ├── overlap.go ├── mutex.go ├── once.go ├── rwmutex.go ├── pinp.go ├── mutex_test.go ├── rwmutex_test.go ├── .github └── workflows │ └── main.yml ├── string.go ├── once_test.go ├── LICENSE └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cristaloleg/sabotage 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /sabotage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cristaloleg/sabotage/HEAD/sabotage.jpg -------------------------------------------------------------------------------- /pinp_test.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func TestPin(t *testing.T) { 10 | n := Pin() 11 | Unpin() 12 | t.Log(n) 13 | 14 | gomax := runtime.GOMAXPROCS(0) 15 | 16 | var wg sync.WaitGroup 17 | wg.Add(gomax) 18 | for i := 0; i < gomax; i++ { 19 | go func() { 20 | defer wg.Done() 21 | n := Pin() 22 | Unpin() 23 | 24 | t.Log(n) 25 | }() 26 | } 27 | wg.Wait() 28 | } 29 | -------------------------------------------------------------------------------- /gid.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "bytes" 5 | "runtime" 6 | "strconv" 7 | ) 8 | 9 | // GoroutineID returns the goroutine id of the calling goroutine. 10 | func GoroutineID() uint64 { 11 | b := make([]byte, 64) 12 | b = b[:runtime.Stack(b, false)] 13 | b = bytes.TrimPrefix(b, []byte("goroutine ")) 14 | b = b[:bytes.IndexByte(b, ' ')] 15 | 16 | n, err := strconv.ParseUint(string(b), 10, 64) 17 | if err != nil { 18 | panic(err) 19 | } 20 | return n 21 | } 22 | -------------------------------------------------------------------------------- /overlap.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import "unsafe" 4 | 5 | // HasOverlap reports whether x and y share memory at any index. 6 | // The memory beyond the slice length is ignored. 7 | // 8 | // Based on golang.org/x/crypto/internal/subtle 9 | // 10 | func HasOverlap(x, y []byte) bool { 11 | return len(x) > 0 && len(y) > 0 && 12 | uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) && 13 | uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1])) 14 | } 15 | -------------------------------------------------------------------------------- /mutex.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | type mutexSpy struct { 10 | state int32 11 | sema uint32 12 | } 13 | 14 | // IsMutexLocked will return true if sync.Mutex is locked. 15 | func IsMutexLocked(m *sync.Mutex) bool { 16 | spy := (*mutexSpy)(unsafe.Pointer(m)) 17 | return atomic.LoadInt32(&spy.state) == 1 18 | } 19 | 20 | // UnlockMutex will unlock a mutex. 21 | func UnlockMutex(mutex *sync.Mutex) { 22 | spy := (*mutexSpy)(unsafe.Pointer(mutex)) 23 | atomic.StoreInt32(&spy.state, 0) 24 | } 25 | -------------------------------------------------------------------------------- /once.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | type onceSpy struct { 10 | done uint32 11 | m sync.Mutex 12 | } 13 | 14 | // ResetSyncOnce will reset sync.Once to default state. 15 | func ResetSyncOnce(once *sync.Once) { 16 | spy := (*onceSpy)(unsafe.Pointer(once)) 17 | atomic.StoreUint32(&spy.done, 0) 18 | } 19 | 20 | // IsOnceDone will return true if sync.Once.Do was invoked. 21 | func IsOnceDone(once *sync.Once) bool { 22 | spy := (*onceSpy)(unsafe.Pointer(once)) 23 | return atomic.LoadUint32(&spy.done) == 1 24 | } 25 | -------------------------------------------------------------------------------- /rwmutex.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "unsafe" 7 | ) 8 | 9 | type rwmutexSpy struct { 10 | state int32 11 | sema uint32 12 | } 13 | 14 | // IsRWMutexLocked will return true if sync.Mutex is locked. 15 | func IsRWMutexLocked(m *sync.RWMutex) bool { 16 | spy := (*rwmutexSpy)(unsafe.Pointer(m)) 17 | return atomic.LoadInt32(&spy.state) == 1 18 | } 19 | 20 | // UnlockRWMutex will unlock rwmutex. 21 | func UnlockRWMutex(mutex *sync.RWMutex) { 22 | spy := (*rwmutexSpy)(unsafe.Pointer(mutex)) 23 | atomic.StoreInt32(&spy.state, 0) 24 | } 25 | -------------------------------------------------------------------------------- /pinp.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | _ "unsafe" 5 | ) 6 | 7 | // Pin the current P, returns pid. 8 | func Pin() int { 9 | return runtime_procPin() 10 | } 11 | 12 | // Unpin the current P. 13 | func Unpin() { 14 | runtime_procUnpin() 15 | } 16 | 17 | // Pid returns the id of a current {}. 18 | func Pid() int { 19 | id := runtime_procPin() 20 | runtime_procUnpin() 21 | return id 22 | } 23 | 24 | //go:noescape 25 | //go:linkname runtime_procPin runtime.procPin 26 | func runtime_procPin() int 27 | 28 | //go:noescape 29 | //go:linkname runtime_procUnpin runtime.procUnpin 30 | func runtime_procUnpin() 31 | -------------------------------------------------------------------------------- /mutex_test.go: -------------------------------------------------------------------------------- 1 | package sabotage_test 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/cristaloleg/sabotage" 8 | ) 9 | 10 | func TestUnlockMutex(t *testing.T) { 11 | mu := &sync.Mutex{} 12 | ch := make(chan struct{}) 13 | 14 | go func() { 15 | mu.Lock() 16 | ch <- struct{}{} 17 | }() 18 | 19 | sabotage.UnlockMutex(mu) 20 | <-ch 21 | } 22 | 23 | func TestIsMutexLocked(t *testing.T) { 24 | mu := &sync.Mutex{} 25 | 26 | if sabotage.IsMutexLocked(mu) { 27 | t.Error("should be false") 28 | } 29 | 30 | mu.Lock() 31 | 32 | if !sabotage.IsMutexLocked(mu) { 33 | t.Error("should be true") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rwmutex_test.go: -------------------------------------------------------------------------------- 1 | package sabotage_test 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/cristaloleg/sabotage" 8 | ) 9 | 10 | func TestUnlockRWMutex(t *testing.T) { 11 | mu := &sync.RWMutex{} 12 | ch := make(chan struct{}) 13 | 14 | go func() { 15 | mu.Lock() 16 | ch <- struct{}{} 17 | }() 18 | 19 | sabotage.UnlockRWMutex(mu) 20 | <-ch 21 | } 22 | 23 | func TestIsRWMutexLocked(t *testing.T) { 24 | mu := &sync.RWMutex{} 25 | 26 | if sabotage.IsRWMutexLocked(mu) { 27 | t.Error("should be false") 28 | } 29 | 30 | mu.Lock() 31 | 32 | if !sabotage.IsRWMutexLocked(mu) { 33 | t.Error("should be true") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Go 4 | jobs: 5 | 6 | test: 7 | strategy: 8 | matrix: 9 | go-version: [1.13.4] 10 | platform: [ubuntu-latest, macos-latest, windows-latest] 11 | runs-on: ${{ matrix.platform }} 12 | steps: 13 | - name: Install Go 14 | uses: actions/setup-go@v1 15 | with: 16 | go-version: ${{ matrix.go-version }} 17 | - name: Checkout code 18 | uses: actions/checkout@v1 19 | - name: Download Go dependencies 20 | env: 21 | GOPROXY: "https://proxy.golang.org" 22 | run: go mod download 23 | - name: Test 24 | run: go test -count=1 ./... 25 | - name: Test with -short -race 26 | run: go test -race -count=1 ./... 27 | 28 | - name: gofmt check 29 | run: diff <(echo -n) <(gofmt -d .) 30 | if: matrix.platform == 'ubuntu-latest' 31 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package sabotage 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "unsafe" 7 | ) 8 | 9 | // BytesToString converts []byte to a string without an allocation. 10 | func BytesToString(bytes []byte) (s string) { 11 | // Also can be just one line: 12 | // return *(*string)(unsafe.Pointer(&bs)) 13 | // See: https://github.com/golang/go/issues/25484 14 | 15 | slice := (*reflect.SliceHeader)(unsafe.Pointer(&bytes)) 16 | str := (*reflect.StringHeader)(unsafe.Pointer(&s)) 17 | str.Data = slice.Data 18 | str.Len = slice.Len 19 | runtime.KeepAlive(&bytes) 20 | return s 21 | } 22 | 23 | // StringToBytes converts string to []byte without an allocation. 24 | func StringToBytes(s string) []byte { 25 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 26 | bh := reflect.SliceHeader{ 27 | Data: sh.Data, 28 | Len: sh.Len, 29 | Cap: sh.Len, 30 | } 31 | return *(*[]byte)(unsafe.Pointer(&bh)) 32 | } 33 | -------------------------------------------------------------------------------- /once_test.go: -------------------------------------------------------------------------------- 1 | package sabotage_test 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/cristaloleg/sabotage" 8 | ) 9 | 10 | func TestResetSyncOnce(t *testing.T) { 11 | once := &sync.Once{} 12 | 13 | counter := 0 14 | 15 | work := func(param int) { 16 | wg := sync.WaitGroup{} 17 | wg.Add(10) 18 | for i := 0; i < 10; i++ { 19 | go func() { 20 | defer wg.Done() 21 | 22 | once.Do(func() { 23 | counter += param 24 | }) 25 | }() 26 | } 27 | wg.Wait() 28 | } 29 | 30 | work(42) 31 | if want := 42; counter != want { 32 | t.Fatalf("expected %v, got %v", want, counter) 33 | } 34 | 35 | sabotage.ResetSyncOnce(once) 36 | 37 | counter = 0 38 | work(78) 39 | if want := 78; counter != want { 40 | t.Fatalf("expected %v, got %v", want, counter) 41 | } 42 | } 43 | 44 | func TestIsOnceDone(t *testing.T) { 45 | once := &sync.Once{} 46 | 47 | if sabotage.IsOnceDone(once) { 48 | t.Error("should be false") 49 | } 50 | 51 | once.Do(func() {}) 52 | 53 | if !sabotage.IsOnceDone(once) { 54 | t.Error("should be true") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Oleg Kovalov 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SABOTAGE 2 | 3 | [![Build Status](https://travis-ci.org/cristaloleg/sabotage.svg)](https://travis-ci.org/cristaloleg/sabotage) 4 | [![GoDoc](https://godoc.org/github.com/cristaloleg/sabotage?status.svg)](https://godoc.org/github.com/cristaloleg/sabotage) 5 | [![Go Report](https://goreportcard.com/badge/github.com/cristaloleg/sabotage)](https://goreportcard.com/report/github.com/cristaloleg/sabotage) 6 | 7 | Collection of dirty hacks in Go. 8 | 9 | 1. Repeat `sync.Once` once more. 10 | 2. Check if `sync.Mutex`/`sync.RWMutex` is locked. 11 | 3. `[]byte <-> string` conversion without additional allocation. 12 | 13 | ![logo](https://raw.githubusercontent.com/cristaloleg/sabotage/master/sabotage.jpg) 14 | 15 | Cause what you see, you might not get 16 | And we can bet, so don't you get souped yet 17 | Scheming on a thing, that's a mirage 18 | I'm trying to tell you now, it's sabotage 19 | 20 | Whhhhhyyyyyy?????? 21 | 22 | (c) Beastie Boys - Sabotage 23 | 24 | ## Examples 25 | 26 | 1. You can repeat `sync.Once` as much as you want: 27 | 28 | ```go 29 | var once sync.Once 30 | 31 | once.Do(myFunc) 32 | 33 | sabotage.ResetSyncOnce(&once) 34 | ``` 35 | 36 | or check if it was invoked earlier: 37 | 38 | ```go 39 | if sabotage.IsOnceDone(&once) { 40 | println("well-well-well") 41 | } 42 | ``` 43 | 44 | 2. You can unlock any mutex from anywhere: 45 | 46 | ```go 47 | var mu sync.Mutex 48 | 49 | // let's yolo begins 50 | sabotage.UnlockMutex(&mu) 51 | ``` 52 | 53 | or check if it's locked: 54 | 55 | ```go 56 | if sabotage.IsMutexLocked(&mu) { 57 | for { 58 | // let's wait a bit 59 | } 60 | } 61 | ``` 62 | --------------------------------------------------------------------------------