From 1d1efefa152a3d2000208a26f13e492f9c09460a Mon Sep 17 00:00:00 2001 From: slasher Date: Wed, 19 Jun 2024 20:30:03 +0800 Subject: [PATCH] perf(util): add mutex with run function safely . #22309205 Signed-off-by: slasher --- blobstore/util/mutex/example_test.go | 98 ++++++++++++++++++++++ blobstore/util/mutex/mutex.go | 116 +++++++++++++++++++++++++++ blobstore/util/mutex/mutex_test.go | 78 ++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 blobstore/util/mutex/example_test.go create mode 100644 blobstore/util/mutex/mutex.go create mode 100644 blobstore/util/mutex/mutex_test.go diff --git a/blobstore/util/mutex/example_test.go b/blobstore/util/mutex/example_test.go new file mode 100644 index 000000000..99075ddf1 --- /dev/null +++ b/blobstore/util/mutex/example_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 The CubeFS Authors. +// +// 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 +// +// http://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. + +package mutex_test + +import ( + "fmt" + "sync" + + "github.com/cubefs/cubefs/blobstore/util/mutex" +) + +func ExampleMutex() { + var m mutex.Mutex + m.Lock() + fmt.Println("mutex locked") + m.Unlock() + + err := m.WithLock(func() error { return mutex.Nil }) + fmt.Println("mutex got error:", err) + + fmt.Println() + l := mutex.NewLocker(&sync.Mutex{}) + l.Lock() + fmt.Println("locker locked") + l.Unlock() + + err = l.WithLock(func() error { return fmt.Errorf("locker error") }) + fmt.Println("locker got error:", err) + + // Output: + // mutex locked + // mutex got error: mutex.Nil + // + // locker locked + // locker got error: locker error +} + +func ExampleRWMutex() { + var m mutex.RWMutex + m.Lock() + fmt.Println("rwmutex locked") + m.Unlock() + + m.RLock() + fmt.Println("rwmutex rlocked") + m.RUnlock() + + err := m.WithLock(func() error { return mutex.Nil }) + fmt.Println("rwmutex got error:", err) + err = m.WithRLock(func() error { return nil }) + fmt.Println("rwmutex got nil:", err == nil) + + rl := m.RLocker() + rl.Lock() + rl.Lock() + fmt.Println("rwmutex RLock as Lock") + rl.Unlock() + rl.Unlock() + + fmt.Println() + l := mutex.NewRLocker(&sync.RWMutex{}) + l.Lock() + fmt.Println("rlocker locked") + l.Unlock() + + l.RLock() + fmt.Println("rlocker rlocked") + l.RUnlock() + + err = l.WithLock(func() error { return mutex.Nil }) + fmt.Println("rlocker got error:", err) + err = l.WithRLock(func() error { return nil }) + fmt.Println("rlocker got nil:", err == nil) + + // Output: + // rwmutex locked + // rwmutex rlocked + // rwmutex got error: mutex.Nil + // rwmutex got nil: true + // rwmutex RLock as Lock + // + // rlocker locked + // rlocker rlocked + // rlocker got error: mutex.Nil + // rlocker got nil: true +} diff --git a/blobstore/util/mutex/mutex.go b/blobstore/util/mutex/mutex.go new file mode 100644 index 000000000..808eaee0c --- /dev/null +++ b/blobstore/util/mutex/mutex.go @@ -0,0 +1,116 @@ +// Copyright 2024 The CubeFS Authors. +// +// 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 +// +// http://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. + +package mutex + +import ( + "sync" +) + +// TODO: add TryLock and TryRLock upgrating to Go1.18+ + +type nilError struct{} + +func (nilError) Error() string { return "mutex.Nil" } + +// Nil is the error which you want an error in function WithLock or WithRLock, +// and then returns it outside to do other choice. +var Nil error = nilError{} + +// Locker represents an object that can be locked, unlocked. +type Locker interface { + sync.Locker +} + +// WithLocker represents a function to run with locker. +type WithLocker interface { + Locker + // WithLock runs function in lock. + WithLock(func() error) error +} + +// RLocker represents a reader/writer mutual exclusion locker. +type RLocker interface { + Locker + RLock() + RUnlock() +} + +// WithRLocker represents a function to run with reading locker. +type WithRLocker interface { + RLocker + WithLocker + // WithRLock runs function in reading lock. + WithRLock(func() error) error +} + +// Mutex is a WithLocker with sync.Mutex. +type Mutex struct{ sync.Mutex } + +var _ WithLocker = (*Mutex)(nil) + +func (m *Mutex) WithLock(f func() error) error { + m.Lock() + defer m.Unlock() + return f() +} + +type mutex struct{ Locker } + +func (m *mutex) WithLock(f func() error) error { + m.Locker.Lock() + defer m.Locker.Unlock() + return f() +} + +// NewLocker returns a WithLocker with the Locker. +func NewLocker(locker Locker) WithLocker { + return &mutex{Locker: locker} +} + +// RWMutex is a WithRLocker with sync.RWMutex. +type RWMutex struct{ sync.RWMutex } + +var _ WithRLocker = (*RWMutex)(nil) + +func (m *RWMutex) WithLock(f func() error) error { + m.Lock() + defer m.Unlock() + return f() +} + +func (m *RWMutex) WithRLock(f func() error) error { + m.RLock() + defer m.RUnlock() + return f() +} + +type rwMutex struct{ RLocker } + +func (m *rwMutex) WithLock(f func() error) error { + m.RLocker.Lock() + defer m.RLocker.Unlock() + return f() +} + +func (m *rwMutex) WithRLock(f func() error) error { + m.RLocker.RLock() + defer m.RLocker.RUnlock() + return f() +} + +// NewRLocker returns a WithRLocker with the RLocker. +func NewRLocker(rlocker RLocker) WithRLocker { + return &rwMutex{RLocker: rlocker} +} diff --git a/blobstore/util/mutex/mutex_test.go b/blobstore/util/mutex/mutex_test.go new file mode 100644 index 000000000..272942842 --- /dev/null +++ b/blobstore/util/mutex/mutex_test.go @@ -0,0 +1,78 @@ +// Copyright 2024 The CubeFS Authors. +// +// 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 +// +// http://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. + +package mutex_test + +import ( + "errors" + "testing" + + "github.com/cubefs/cubefs/blobstore/util/mutex" + "github.com/stretchr/testify/require" +) + +func TestMutexWith(t *testing.T) { + var m mutex.Mutex + val := -1 + f := func() error { + val++ + switch val { + case 0: + return nil + case 1: + return mutex.Nil + default: + return errors.New("") + } + } + require.Nil(t, m.WithLock(f)) + nilErr := m.WithLock(f) + require.ErrorIs(t, nilErr, mutex.Nil) + require.Equal(t, mutex.Nil, nilErr) + require.Error(t, m.WithLock(f)) +} + +func Benchmark_Span_Assertion(b *testing.B) { + var m mutex.RWMutex + f := func() error { return nil } + b.Run("Lock", func(b *testing.B) { + b.ResetTimer() + for ii := 0; ii <= b.N; ii++ { + m.Lock() + _ = struct{}{} + m.Unlock() + } + }) + b.Run("WithLock", func(b *testing.B) { + b.ResetTimer() + for ii := 0; ii <= b.N; ii++ { + m.WithLock(f) + } + }) + + b.Run("RLock", func(b *testing.B) { + b.ResetTimer() + for ii := 0; ii <= b.N; ii++ { + m.RLock() + _ = struct{}{} + m.RUnlock() + } + }) + b.Run("WithRLock", func(b *testing.B) { + b.ResetTimer() + for ii := 0; ii <= b.N; ii++ { + m.WithRLock(f) + } + }) +}