summaryrefslogtreecommitdiffhomepage
path: root/syncs
diff options
context:
space:
mode:
authorAndrew Dunham <andrew@du.nham.ca>2025-11-11 15:27:58 -0500
committerAndrew Dunham <andrew@du.nham.ca>2025-11-11 15:27:58 -0500
commit67dfdae59dafdf326cbc2a4d31472c14b2cf1393 (patch)
tree3873fb93bd486b45aec8d57fde77a0f7ac383ad8 /syncs
parent85cb64c4ff0537b5722f2df84393ef4d8c4c83ad (diff)
downloadtailscale-andrew/syncs-tsync.tar.xz
tailscale-andrew/syncs-tsync.zip
syncs/tsync: WIP on initial packageandrew/syncs-tsync
Change-Id: Ie1519455d82aabdd7f86905861aea373ab2d9d68
Diffstat (limited to 'syncs')
-rw-r--r--syncs/tsync/tsync.go1
-rw-r--r--syncs/tsync/tsync_noop.go10
-rw-r--r--syncs/tsync/tsync_real.go108
3 files changed, 119 insertions, 0 deletions
diff --git a/syncs/tsync/tsync.go b/syncs/tsync/tsync.go
new file mode 100644
index 000000000..0cda18be8
--- /dev/null
+++ b/syncs/tsync/tsync.go
@@ -0,0 +1 @@
+package tsync
diff --git a/syncs/tsync/tsync_noop.go b/syncs/tsync/tsync_noop.go
new file mode 100644
index 000000000..a536a9064
--- /dev/null
+++ b/syncs/tsync/tsync_noop.go
@@ -0,0 +1,10 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !ts_tsync_test
+
+package tsync
+
+import "sync"
+
+type Mutex = sync.Mutex
diff --git a/syncs/tsync/tsync_real.go b/syncs/tsync/tsync_real.go
new file mode 100644
index 000000000..b81346fd2
--- /dev/null
+++ b/syncs/tsync/tsync_real.go
@@ -0,0 +1,108 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build ts_tsync_test
+
+package tsync
+
+import (
+ "fmt"
+ "sync"
+ "weak"
+)
+
+type Mutex struct {
+ mu sync.Mutex
+}
+
+func (m *Mutex) Lock() {
+ noteLock(m)
+ m.mu.Lock()
+}
+
+func (m *Mutex) Unlock() {
+ noteUnlock(m)
+ m.mu.Unlock()
+}
+
+func (m *Mutex) TryLock() bool {
+ locked := m.mu.TryLock()
+ if locked {
+ noteLock(m)
+ }
+ return locked
+}
+
+type lockInfo struct {
+ locked bool
+}
+
+var (
+ // TODO: this should be a per-G datastructure
+ locksMu sync.Mutex
+ locks map[weak.Pointer[Mutex]]*lockInfo
+)
+
+func panicLocked(format string, args ...any) {
+ msg := fmt.Sprintf(format, args...)
+
+ // First, gather all current (non-GCed) locks.
+ type lockEntry struct {
+ wp weak.Pointer[Mutex]
+ li *lockInfo
+ }
+ var currLocks []lockEntry
+ for wp, li := range locks {
+ if wp.Value() != nil {
+ currLocks = append(currLocks, lockEntry{wp, li})
+ }
+ }
+
+ msg += fmt.Sprintf("\ncurrent locks (%d):", len(currLocks))
+ for _, cl := range currLocks {
+ if cl.li.locked {
+ msg += fmt.Sprintf("\n\tlocked: %p", cl.wp.Value())
+ } else {
+ msg += fmt.Sprintf("\n\tunlocked: %p", cl.wp.Value())
+ }
+ }
+ panic(msg)
+}
+
+func noteLock(m *Mutex) {
+ locksMu.Lock()
+ defer locksMu.Unlock()
+ if locks == nil {
+ locks = make(map[weak.Pointer[Mutex]]*lockInfo)
+ }
+
+ wp := weak.Make(m)
+ li, ok := locks[wp]
+ if !ok {
+ locks[wp] = &lockInfo{
+ locked: true,
+ }
+ return
+ }
+
+ li.locked = true
+
+ // TODO: additional checks here
+ // TODO: clear things out of the locks map when GCed
+}
+
+func noteUnlock(m *Mutex) {
+ locksMu.Lock()
+ defer locksMu.Unlock()
+
+ wp := weak.Make(m)
+ li, ok := locks[wp]
+ if !ok {
+ panicLocked("unknown Unlock on mutex %p", m)
+ }
+
+ li.locked = false
+
+ // TODO: additional checks here
+ // TODO: clear things out of the locks map when GCed
+}