Skip to content

Commit

Permalink
Introduce an atomic type-safe wrapper around error type. (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeypavlenko authored and prashantv committed Sep 27, 2018
1 parent ca68046 commit bb9a8ed
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 0 deletions.
55 changes: 55 additions & 0 deletions error.go
@@ -0,0 +1,55 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package atomic

// Error is an atomic type-safe wrapper around Value for errors
type Error struct{ v Value }

// errorHolder is non-nil holder for error object.
// atomic.Value panics on saving nil object, so err object needs to be
// wrapped with valid object first.
type errorHolder struct{ err error }

// NewError creates new atomic error object
func NewError(err error) *Error {
e := &Error{}
if err != nil {
e.Store(err)
}
return e
}

// Load atomically loads the wrapped error
func (e *Error) Load() error {
v := e.v.Load()
if v == nil {
return nil
}

eh := v.(errorHolder)
return eh.err
}

// Store atomically stores error.
// NOTE: a holder object is allocated on each Store call.
func (e *Error) Store(err error) {
e.v.Store(errorHolder{err: err})
}
55 changes: 55 additions & 0 deletions error_test.go
@@ -0,0 +1,55 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package atomic

import (
"errors"
"testing"

"github.com/stretchr/testify/require"
)

func TestErrorByValue(t *testing.T) {
err := &Error{}
require.Nil(t, err.Load(), "Initial value shall be nil")
}

func TestNewErrorWithNilArgument(t *testing.T) {
err := NewError(nil)
require.Nil(t, err.Load(), "Initial value shall be nil")
}

func TestErrorCanStoreNil(t *testing.T) {
err := NewError(errors.New("hello"))
err.Store(nil)
require.Nil(t, err.Load(), "Stored value shall be nil")
}

func TestNewErrorWithError(t *testing.T) {
err1 := errors.New("hello1")
err2 := errors.New("hello2")

atom := NewError(err1)
require.Equal(t, err1, atom.Load(), "Expected Load to return initialized value")

atom.Store(err2)
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value")
}
16 changes: 16 additions & 0 deletions stress_test.go
Expand Up @@ -21,6 +21,7 @@
package atomic

import (
"errors"
"math"
"runtime"
"sync"
Expand All @@ -46,6 +47,7 @@ var _stressTests = map[string]func() func(){
"bool": stressBool,
"string": stressString,
"duration": stressDuration,
"error": stressError,
}

func TestStress(t *testing.T) {
Expand Down Expand Up @@ -256,3 +258,17 @@ func stressDuration() func() {
atom.Store(1)
}
}

func stressError() func() {
var atom = NewError(nil)
var err1 = errors.New("err1")
var err2 = errors.New("err2")
return func() {
atom.Load()
atom.Store(err1)
atom.Load()
atom.Store(err2)
atom.Load()
atom.Store(nil)
}
}

0 comments on commit bb9a8ed

Please sign in to comment.