104 changed files with 6368 additions and 125 deletions
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
package app |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"encoders.orly/envelopes/eventenvelope" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
utils "utils.orly" |
||||
) |
||||
|
||||
func (l *Listener) HandleEvent(c context.Context, msg []byte) ( |
||||
err error, |
||||
) { |
||||
// decode the envelope
|
||||
env := eventenvelope.NewSubmission() |
||||
if msg, err = env.Unmarshal(msg); chk.E(err) { |
||||
return |
||||
} |
||||
if len(msg) > 0 { |
||||
log.I.F("extra '%s'", msg) |
||||
} |
||||
// check the event ID is correct
|
||||
calculatedId := env.E.GetIDBytes() |
||||
if !utils.FastEqual(calculatedId, env.E.ID) { |
||||
if err = Ok.Invalid( |
||||
l, env, "event id is computed incorrectly, "+ |
||||
"event has ID %0x, but when computed it is %0x", |
||||
env.E.ID, calculatedId, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
// verify the signature
|
||||
var ok bool |
||||
if ok, err = env.Verify(); chk.T(err) { |
||||
if err = Ok.Error( |
||||
l, env, fmt.Sprintf( |
||||
"failed to verify signature: %s", |
||||
err.Error(), |
||||
), |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
} else if !ok { |
||||
if err = Ok.Invalid( |
||||
l, env, |
||||
"signature is invalid", |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
// store the event
|
||||
if _, _, err = l.SaveEvent(c, env.E, false, nil); chk.E(err) { |
||||
return |
||||
} |
||||
// Send a success response after storing
|
||||
if err = Ok.Ok(l, env, ""); chk.E(err) { |
||||
return |
||||
} |
||||
log.D.F("saved event %0x", env.E.ID) |
||||
return |
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
package app |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
|
||||
"encoders.orly/envelopes/closedenvelope" |
||||
"encoders.orly/envelopes/eoseenvelope" |
||||
"encoders.orly/envelopes/eventenvelope" |
||||
"encoders.orly/envelopes/reqenvelope" |
||||
"encoders.orly/event" |
||||
"encoders.orly/filter" |
||||
"encoders.orly/tag" |
||||
"github.com/dgraph-io/badger/v4" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
"utils.orly/normalize" |
||||
"utils.orly/pointers" |
||||
) |
||||
|
||||
func (l *Listener) HandleReq(c context.Context, msg []byte) ( |
||||
err error, |
||||
) { |
||||
var rem []byte |
||||
env := reqenvelope.New() |
||||
if rem, err = env.Unmarshal(msg); chk.E(err) { |
||||
return normalize.Error.Errorf(err.Error()) |
||||
} |
||||
if len(rem) > 0 { |
||||
log.I.F("extra '%s'", rem) |
||||
} |
||||
var events event.S |
||||
for _, f := range *env.Filters { |
||||
if pointers.Present(f.Limit) { |
||||
if *f.Limit == 0 { |
||||
continue |
||||
} |
||||
} |
||||
if events, err = l.QueryEvents(c, f); chk.E(err) { |
||||
if errors.Is(err, badger.ErrDBClosed) { |
||||
return |
||||
} |
||||
err = nil |
||||
} |
||||
} |
||||
// write out the events to the socket
|
||||
seen := make(map[string]struct{}) |
||||
for _, ev := range events { |
||||
// track the IDs we've sent
|
||||
seen[string(ev.ID)] = struct{}{} |
||||
var res *eventenvelope.Result |
||||
if res, err = eventenvelope.NewResultWith( |
||||
env.Subscription, ev, |
||||
); chk.E(err) { |
||||
return |
||||
} |
||||
if err = res.Write(l); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
// write the EOSE to signal to the client that all events found have been
|
||||
// sent.
|
||||
if err = eoseenvelope.NewFrom(env.Subscription). |
||||
Write(l); chk.E(err) { |
||||
return |
||||
} |
||||
// if the query was for just Ids, we know there can't be any more results,
|
||||
// so cancel the subscription.
|
||||
cancel := true |
||||
var subbedFilters filter.S |
||||
for _, f := range *env.Filters { |
||||
if f.Ids.Len() < 1 { |
||||
cancel = false |
||||
subbedFilters = append(subbedFilters, f) |
||||
} else { |
||||
// remove the IDs that we already sent
|
||||
var notFounds [][]byte |
||||
for _, ev := range events { |
||||
if _, ok := seen[string(ev.ID)]; ok { |
||||
continue |
||||
} |
||||
notFounds = append(notFounds, ev.ID) |
||||
} |
||||
// if all were found, don't add to subbedFilters
|
||||
if len(notFounds) == 0 { |
||||
continue |
||||
} |
||||
// rewrite the filter Ids to remove the ones we already sent
|
||||
f.Ids = tag.NewFromBytesSlice(notFounds...) |
||||
// add the filter to the list of filters we're subscribing to
|
||||
subbedFilters = append(subbedFilters, f) |
||||
} |
||||
// also, if we received the limit number of events, subscription ded
|
||||
if pointers.Present(f.Limit) { |
||||
if len(events) < int(*f.Limit) { |
||||
cancel = false |
||||
} |
||||
} |
||||
} |
||||
receiver := make(event.C, 32) |
||||
// if the subscription should be cancelled, do so
|
||||
if !cancel { |
||||
l.publishers.Receive( |
||||
&W{ |
||||
Conn: l.conn, |
||||
remote: l.remote, |
||||
Id: string(env.Subscription), |
||||
Receiver: receiver, |
||||
Filters: env.Filters, |
||||
}, |
||||
) |
||||
} else { |
||||
if err = closedenvelope.NewFrom( |
||||
env.Subscription, nil, |
||||
).Write(l); chk.E(err) { |
||||
return |
||||
} |
||||
} |
||||
return |
||||
} |
||||
@ -1,9 +1,23 @@
@@ -1,9 +1,23 @@
|
||||
package app |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/coder/websocket" |
||||
"lol.mleku.dev/chk" |
||||
) |
||||
|
||||
type Listener struct { |
||||
conn *websocket.Conn |
||||
*Server |
||||
conn *websocket.Conn |
||||
ctx context.Context |
||||
remote string |
||||
} |
||||
|
||||
func (l *Listener) Write(p []byte) (n int, err error) { |
||||
if err = l.conn.Write(l.ctx, websocket.MessageText, p); chk.E(err) { |
||||
return |
||||
} |
||||
n = len(p) |
||||
return |
||||
} |
||||
|
||||
@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
|
||||
package app |
||||
|
||||
import ( |
||||
"encoders.orly/envelopes/eventenvelope" |
||||
"encoders.orly/envelopes/okenvelope" |
||||
"encoders.orly/reason" |
||||
) |
||||
|
||||
// OK represents a function that processes events or operations, using provided
|
||||
// parameters to generate formatted messages and return errors if any issues
|
||||
// occur during processing.
|
||||
type OK func( |
||||
l *Listener, env *eventenvelope.Submission, format string, params ...any, |
||||
) (err error) |
||||
|
||||
// OKs provides a collection of handler functions for managing different types
|
||||
// of operational outcomes, each corresponding to specific error or status
|
||||
// conditions such as authentication requirements, rate limiting, and invalid
|
||||
// inputs.
|
||||
type OKs struct { |
||||
Ok OK |
||||
AuthRequired OK |
||||
PoW OK |
||||
Duplicate OK |
||||
Blocked OK |
||||
RateLimited OK |
||||
Invalid OK |
||||
Error OK |
||||
Unsupported OK |
||||
Restricted OK |
||||
} |
||||
|
||||
// Ok provides a collection of handler functions for managing different types of
|
||||
// operational outcomes, each corresponding to specific error or status
|
||||
// conditions such as authentication requirements, rate limiting, and invalid
|
||||
// inputs.
|
||||
var Ok = OKs{ |
||||
Ok: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), true, nil, |
||||
).Write(l) |
||||
}, |
||||
AuthRequired: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.AuthRequired.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
PoW: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.PoW.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Duplicate: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Duplicate.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Blocked: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Blocked.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
RateLimited: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.RateLimited.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Invalid: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Invalid.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Error: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Error.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Unsupported: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Unsupported.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
Restricted: func( |
||||
l *Listener, env *eventenvelope.Submission, format string, |
||||
params ...any, |
||||
) (err error) { |
||||
return okenvelope.NewFrom( |
||||
env.Id(), false, reason.Restricted.F(format, params...), |
||||
).Write(l) |
||||
}, |
||||
} |
||||
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
package app |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"sync" |
||||
|
||||
"encoders.orly/envelopes/eventenvelope" |
||||
"encoders.orly/event" |
||||
"encoders.orly/filter" |
||||
"github.com/coder/websocket" |
||||
"interfaces.orly/publisher" |
||||
"interfaces.orly/typer" |
||||
"lol.mleku.dev/chk" |
||||
"lol.mleku.dev/log" |
||||
) |
||||
|
||||
const Type = "socketapi" |
||||
|
||||
type Subscription struct { |
||||
remote string |
||||
*filter.S |
||||
} |
||||
|
||||
// Map is a map of filters associated with a collection of ws.Listener
|
||||
// connections.
|
||||
type Map map[*websocket.Conn]map[string]Subscription |
||||
|
||||
type W struct { |
||||
*websocket.Conn |
||||
|
||||
remote string |
||||
|
||||
// If Cancel is true, this is a close command.
|
||||
Cancel bool |
||||
|
||||
// Id is the subscription Id. If Cancel is true, cancel the named
|
||||
// subscription, otherwise, cancel the publisher for the socket.
|
||||
Id string |
||||
|
||||
// The Receiver holds the event channel for receiving notifications or data
|
||||
// relevant to this WebSocket connection.
|
||||
Receiver event.C |
||||
|
||||
// Filters holds a collection of filters used to match or process events
|
||||
// associated with this WebSocket connection. It is used to determine which
|
||||
// notifications or data should be received by the subscriber.
|
||||
Filters *filter.S |
||||
} |
||||
|
||||
func (w *W) Type() (typeName string) { return Type } |
||||
|
||||
// P is a structure that manages subscriptions and associated filters for
|
||||
// websocket listeners. It uses a mutex to synchronize access to a map storing
|
||||
// subscriber connections and their filter configurations.
|
||||
type P struct { |
||||
c context.Context |
||||
// Mx is the mutex for the Map.
|
||||
Mx sync.Mutex |
||||
// Map is the map of subscribers and subscriptions from the websocket api.
|
||||
Map |
||||
} |
||||
|
||||
var _ publisher.I = &P{} |
||||
|
||||
func NewPublisher() (publisher *P) { |
||||
return &P{ |
||||
Map: make(Map), |
||||
} |
||||
} |
||||
|
||||
func (p *P) Type() (typeName string) { return Type } |
||||
|
||||
// Receive handles incoming messages to manage websocket listener subscriptions
|
||||
// and associated filters.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - msg (publisher.Message): The incoming message to process; expected to be of
|
||||
// type *W to trigger subscription management actions.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// - Checks if the message is of type *W.
|
||||
//
|
||||
// - If Cancel is true, removes a subscriber by ID or the entire listener.
|
||||
//
|
||||
// - Otherwise, adds the subscription to the map under a mutex lock.
|
||||
//
|
||||
// - Logs actions related to subscription creation or removal.
|
||||
func (p *P) Receive(msg typer.T) { |
||||
if m, ok := msg.(*W); ok { |
||||
if m.Cancel { |
||||
if m.Id == "" { |
||||
p.removeSubscriber(m.Conn) |
||||
log.T.F("removed listener %s", m.remote) |
||||
} else { |
||||
p.removeSubscriberId(m.Conn, m.Id) |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"removed subscription %s for %s", m.Id, |
||||
m.remote, |
||||
) |
||||
}, |
||||
) |
||||
} |
||||
return |
||||
} |
||||
p.Mx.Lock() |
||||
defer p.Mx.Unlock() |
||||
if subs, ok := p.Map[m.Conn]; !ok { |
||||
subs = make(map[string]Subscription) |
||||
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote} |
||||
p.Map[m.Conn] = subs |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"created new subscription for %s, %s", |
||||
m.remote, |
||||
m.Filters.Marshal(nil), |
||||
) |
||||
}, |
||||
) |
||||
} else { |
||||
subs[m.Id] = Subscription{S: m.Filters, remote: m.remote} |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"added subscription %s for %s", m.Id, |
||||
m.remote, |
||||
) |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Deliver processes and distributes an event to all matching subscribers based on their filter configurations.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - ev (*event.E): The event to be delivered to subscribed clients.
|
||||
//
|
||||
// # Expected behaviour
|
||||
//
|
||||
// Delivers the event to all subscribers whose filters match the event. It
|
||||
// applies authentication checks if required by the server and skips delivery
|
||||
// for unauthenticated users when events are privileged.
|
||||
func (p *P) Deliver(ev *event.E) { |
||||
var err error |
||||
p.Mx.Lock() |
||||
defer p.Mx.Unlock() |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"delivering event %0x to websocket subscribers %d", ev.ID, |
||||
len(p.Map), |
||||
) |
||||
}, |
||||
) |
||||
for w, subs := range p.Map { |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"%v %s", subs, |
||||
) |
||||
}, |
||||
) |
||||
for id, subscriber := range subs { |
||||
if !subscriber.Match(ev) { |
||||
continue |
||||
} |
||||
// if p.Server.AuthRequired() {
|
||||
// if !auth.CheckPrivilege(w.AuthedPubkey(), ev) {
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
var res *eventenvelope.Result |
||||
if res, err = eventenvelope.NewResultWith(id, ev); chk.E(err) { |
||||
continue |
||||
} |
||||
if err = w.Write( |
||||
p.c, websocket.MessageText, res.Marshal(nil), |
||||
); chk.E(err) { |
||||
continue |
||||
} |
||||
log.T.C( |
||||
func() string { |
||||
return fmt.Sprintf( |
||||
"dispatched event %0x to subscription %s, %s", |
||||
ev.ID, id, subscriber.remote, |
||||
) |
||||
}, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// removeSubscriberId removes a specific subscription from a subscriber
|
||||
// websocket.
|
||||
func (p *P) removeSubscriberId(ws *websocket.Conn, id string) { |
||||
p.Mx.Lock() |
||||
var subs map[string]Subscription |
||||
var ok bool |
||||
if subs, ok = p.Map[ws]; ok { |
||||
delete(p.Map[ws], id) |
||||
_ = subs |
||||
if len(subs) == 0 { |
||||
delete(p.Map, ws) |
||||
} |
||||
} |
||||
p.Mx.Unlock() |
||||
} |
||||
|
||||
// removeSubscriber removes a websocket from the P collection.
|
||||
func (p *P) removeSubscriber(ws *websocket.Conn) { |
||||
p.Mx.Lock() |
||||
clear(p.Map[ws]) |
||||
delete(p.Map, ws) |
||||
p.Mx.Unlock() |
||||
} |
||||
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
package reason |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
|
||||
"lol.mleku.dev/log" |
||||
) |
||||
|
||||
// R is the machine-readable prefix before the colon in an OK or CLOSED envelope message.
|
||||
// Below are the most common kinds that are mentioned in NIP-01.
|
||||
type R []byte |
||||
|
||||
var ( |
||||
AuthRequired = R("auth-required") |
||||
PoW = R("pow") |
||||
Duplicate = R("duplicate") |
||||
Blocked = R("blocked") |
||||
RateLimited = R("rate-limited") |
||||
Invalid = R("invalid") |
||||
Error = R("error") |
||||
Unsupported = R("unsupported") |
||||
Restricted = R("restricted") |
||||
) |
||||
|
||||
// S returns the R as a string
|
||||
func (r R) S() string { return string(r) } |
||||
|
||||
// B returns the R as a byte slice.
|
||||
func (r R) B() []byte { return r } |
||||
|
||||
// IsPrefix returns whether a text contains the same R prefix.
|
||||
func (r R) IsPrefix(reason []byte) bool { |
||||
return bytes.HasPrefix( |
||||
reason, r.B(), |
||||
) |
||||
} |
||||
|
||||
// F allows creation of a full R text with a printf style format.
|
||||
func (r R) F(format string, params ...any) (o []byte) { |
||||
log.D.F(format, params...) |
||||
return Msg(r, format, params...) |
||||
} |
||||
|
||||
// Msg constructs a properly formatted message with a machine-readable prefix
|
||||
// for OK and CLOSED envelopes.
|
||||
func Msg(prefix R, format string, params ...any) (o []byte) { |
||||
if len(prefix) < 1 { |
||||
prefix = Error |
||||
} |
||||
return []byte(fmt.Sprintf(prefix.S()+": "+format, params...)) |
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= |
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= |
||||
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= |
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= |
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= |
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= |
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= |
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= |
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= |
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= |
||||
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= |
||||
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg= |
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ= |
||||
go-simpler.org/env v0.12.0 h1:kt/lBts0J1kjWJAnB740goNdvwNxt5emhYngL0Fzufs= |
||||
go-simpler.org/env v0.12.0/go.mod h1:cc/5Md9JCUM7LVLtN0HYjPTDcI3Q8TDaPlNTAlDU+WI= |
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= |
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= |
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
lol.mleku.dev v1.0.2 h1:bSV1hHnkmt1hq+9nSvRwN6wgcI7itbM3XRZ4dMB438c= |
||||
lol.mleku.dev v1.0.2/go.mod h1:DQ0WnmkntA9dPLCXgvtIgYt5G0HSqx3wSTLolHgWeLA= |
||||
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w= |
||||
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q= |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
package publisher |
||||
|
||||
import ( |
||||
"encoders.orly/event" |
||||
"interfaces.orly/typer" |
||||
) |
||||
|
||||
type I interface { |
||||
typer.T |
||||
Deliver(ev *event.E) |
||||
Receive(msg typer.T) |
||||
} |
||||
|
||||
type Publishers []I |
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
// Package typer is an interface for server to use to identify their type simply for
|
||||
// aggregating multiple self-registered server such that the top level can recognise the
|
||||
// type of a message and match it to the type of handler.
|
||||
package typer |
||||
|
||||
type T interface { |
||||
// Type returns a type identifier string to allow multiple self-registering publisher.I to
|
||||
// be used with an abstraction to allow multiple APIs to publish.
|
||||
Type() string |
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
package publish |
||||
|
||||
import ( |
||||
"encoders.orly/event" |
||||
"interfaces.orly/publisher" |
||||
"interfaces.orly/typer" |
||||
) |
||||
|
||||
// S is the control structure for the subscription management scheme.
|
||||
type S struct { |
||||
publisher.Publishers |
||||
} |
||||
|
||||
// New creates a new publish.S.
|
||||
func New(p ...publisher.I) (s *S) { |
||||
s = &S{Publishers: p} |
||||
return |
||||
} |
||||
|
||||
var _ publisher.I = &S{} |
||||
|
||||
func (s *S) Type() string { return "publish" } |
||||
|
||||
func (s *S) Deliver(ev *event.E) { |
||||
for _, p := range s.Publishers { |
||||
p.Deliver(ev) |
||||
} |
||||
} |
||||
|
||||
func (s *S) Receive(msg typer.T) { |
||||
t := msg.Type() |
||||
for _, p := range s.Publishers { |
||||
if p.Type() == t { |
||||
p.Receive(msg) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
coverage: |
||||
range: 80..100 |
||||
round: down |
||||
precision: 2 |
||||
|
||||
status: |
||||
project: # measuring the overall project coverage |
||||
default: # context, you can create multiple ones with custom titles |
||||
enabled: yes # must be yes|true to enable this status |
||||
target: 100 # specify the target coverage for each commit status |
||||
# option: "auto" (must increase from parent commit or pull request base) |
||||
# option: "X%" a static target percentage to hit |
||||
if_not_found: success # if parent is not found report status as success, error, or failure |
||||
if_ci_failed: error # if ci fails report status as success, error, or failure |
||||
|
||||
# Also update COVER_IGNORE_PKGS in the Makefile. |
||||
ignore: |
||||
- /internal/gen-atomicint/ |
||||
- /internal/gen-valuewrapper/ |
||||
@ -0,0 +1,130 @@
@@ -0,0 +1,130 @@
|
||||
# Changelog |
||||
All notable changes to this project will be documented in this file. |
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), |
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
||||
|
||||
## Unreleased |
||||
- No changes yet. |
||||
|
||||
## [1.11.0] - 2023-05-02 |
||||
### Fixed |
||||
- Fix `Swap` and `CompareAndSwap` for `Value` wrappers without initialization. |
||||
|
||||
### Added |
||||
- Add `String` method to `atomic.Pointer[T]` type allowing users to safely print |
||||
underlying values of pointers. |
||||
|
||||
[1.11.0]: https://github.com/uber-go/atomic/compare/v1.10.0...v1.11.0 |
||||
|
||||
## [1.10.0] - 2022-08-11 |
||||
### Added |
||||
- Add `atomic.Float32` type for atomic operations on `float32`. |
||||
- Add `CompareAndSwap` and `Swap` methods to `atomic.String`, `atomic.Error`, |
||||
and `atomic.Value`. |
||||
- Add generic `atomic.Pointer[T]` type for atomic operations on pointers of any |
||||
type. This is present only for Go 1.18 or higher, and is a drop-in for |
||||
replacement for the standard library's `sync/atomic.Pointer` type. |
||||
|
||||
### Changed |
||||
- Deprecate `CAS` methods on all types in favor of corresponding |
||||
`CompareAndSwap` methods. |
||||
|
||||
Thanks to @eNV25 and @icpd for their contributions to this release. |
||||
|
||||
[1.10.0]: https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0 |
||||
|
||||
## [1.9.0] - 2021-07-15 |
||||
### Added |
||||
- Add `Float64.Swap` to match int atomic operations. |
||||
- Add `atomic.Time` type for atomic operations on `time.Time` values. |
||||
|
||||
[1.9.0]: https://github.com/uber-go/atomic/compare/v1.8.0...v1.9.0 |
||||
|
||||
## [1.8.0] - 2021-06-09 |
||||
### Added |
||||
- Add `atomic.Uintptr` type for atomic operations on `uintptr` values. |
||||
- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values. |
||||
|
||||
[1.8.0]: https://github.com/uber-go/atomic/compare/v1.7.0...v1.8.0 |
||||
|
||||
## [1.7.0] - 2020-09-14 |
||||
### Added |
||||
- Support JSON serialization and deserialization of primitive atomic types. |
||||
- Support Text marshalling and unmarshalling for string atomics. |
||||
|
||||
### Changed |
||||
- Disallow incorrect comparison of atomic values in a non-atomic way. |
||||
|
||||
### Removed |
||||
- Remove dependency on `golang.org/x/{lint, tools}`. |
||||
|
||||
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0 |
||||
|
||||
## [1.6.0] - 2020-02-24 |
||||
### Changed |
||||
- Drop library dependency on `golang.org/x/{lint, tools}`. |
||||
|
||||
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0 |
||||
|
||||
## [1.5.1] - 2019-11-19 |
||||
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together |
||||
causing `CAS` to fail even though the old value matches. |
||||
|
||||
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1 |
||||
|
||||
## [1.5.0] - 2019-10-29 |
||||
### Changed |
||||
- With Go modules, only the `go.uber.org/atomic` import path is supported now. |
||||
If you need to use the old import path, please add a `replace` directive to |
||||
your `go.mod`. |
||||
|
||||
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0 |
||||
|
||||
## [1.4.0] - 2019-05-01 |
||||
### Added |
||||
- Add `atomic.Error` type for atomic operations on `error` values. |
||||
|
||||
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0 |
||||
|
||||
## [1.3.2] - 2018-05-02 |
||||
### Added |
||||
- Add `atomic.Duration` type for atomic operations on `time.Duration` values. |
||||
|
||||
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2 |
||||
|
||||
## [1.3.1] - 2017-11-14 |
||||
### Fixed |
||||
- Revert optimization for `atomic.String.Store("")` which caused data races. |
||||
|
||||
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1 |
||||
|
||||
## [1.3.0] - 2017-11-13 |
||||
### Added |
||||
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools. |
||||
|
||||
### Changed |
||||
- Optimize `atomic.String.Store("")` by avoiding an allocation. |
||||
|
||||
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0 |
||||
|
||||
## [1.2.0] - 2017-04-12 |
||||
### Added |
||||
- Shadow `atomic.Value` from `sync/atomic`. |
||||
|
||||
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0 |
||||
|
||||
## [1.1.0] - 2017-03-10 |
||||
### Added |
||||
- Add atomic `Float64` type. |
||||
|
||||
### Changed |
||||
- Support new `go.uber.org/atomic` import path. |
||||
|
||||
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0 |
||||
|
||||
## [1.0.0] - 2016-07-18 |
||||
|
||||
- Initial release. |
||||
|
||||
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0 |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
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. |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
# Directory to place `go install`ed binaries into.
|
||||
export GOBIN ?= $(shell pwd)/bin
|
||||
|
||||
GOLINT = $(GOBIN)/golint
|
||||
GEN_ATOMICINT = $(GOBIN)/gen-atomicint
|
||||
GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper
|
||||
STATICCHECK = $(GOBIN)/staticcheck
|
||||
|
||||
GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
|
||||
|
||||
# Also update ignore section in .codecov.yml.
|
||||
COVER_IGNORE_PKGS = \
|
||||
github.com/p9ds/atomic/internal/gen-atomicint \
|
||||
github.com/p9ds/atomic/internal/gen-atomicwrapper
|
||||
|
||||
.PHONY: build |
||||
build: |
||||
go build ./...
|
||||
|
||||
.PHONY: test |
||||
test: |
||||
go test -race ./...
|
||||
|
||||
.PHONY: gofmt |
||||
gofmt: |
||||
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
|
||||
gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
|
||||
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false)
|
||||
|
||||
$(GOLINT): |
||||
cd tools && go install golang.org/x/lint/golint
|
||||
|
||||
$(STATICCHECK): |
||||
cd tools && go install honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*) |
||||
go build -o $@ ./internal/gen-atomicwrapper
|
||||
|
||||
$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*) |
||||
go build -o $@ ./internal/gen-atomicint
|
||||
|
||||
.PHONY: golint |
||||
golint: $(GOLINT) |
||||
$(GOLINT) ./...
|
||||
|
||||
.PHONY: staticcheck |
||||
staticcheck: $(STATICCHECK) |
||||
$(STATICCHECK) ./...
|
||||
|
||||
.PHONY: lint |
||||
lint: gofmt golint staticcheck generatenodirty |
||||
|
||||
# comma separated list of packages to consider for code coverage.
|
||||
COVER_PKG = $(shell \
|
||||
go list -find ./... | \
|
||||
grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \
|
||||
paste -sd, -)
|
||||
|
||||
.PHONY: cover |
||||
cover: |
||||
go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./...
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
.PHONY: generate |
||||
generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER) |
||||
go generate ./...
|
||||
|
||||
.PHONY: generatenodirty |
||||
generatenodirty: |
||||
@[ -z "$$(git status --porcelain)" ] || ( \
|
||||
echo "Working tree is dirty. Commit your changes first."; \
|
||||
git status; \
|
||||
exit 1 )
|
||||
@make generate
|
||||
@status=$$(git status --porcelain); \
|
||||
[ -z "$$status" ] || ( \
|
||||
echo "Working tree is dirty after `make generate`:"; \
|
||||
echo "$$status"; \
|
||||
echo "Please ensure that the generated code is up-to-date." )
|
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
# atomic |
||||
|
||||
Simple wrappers for primitive types to enforce atomic access. |
||||
|
||||
## Installation |
||||
|
||||
```shell |
||||
$ go get -u github.com/mleku/nodl/pkg/atomic@latest |
||||
``` |
||||
|
||||
## Usage |
||||
|
||||
The standard library's `sync/atomic` is powerful, but it's easy to forget which |
||||
variables must be accessed atomically. `github.com/mleku/nodl/pkg/atomic` preserves all the |
||||
functionality of the standard library, but wraps the primitive types to |
||||
provide a safer, more convenient API. |
||||
|
||||
```go |
||||
var atom atomic.Uint32 |
||||
atom.Store(42) |
||||
atom.Sub(2) |
||||
atom.CompareAndSwap(40, 11) |
||||
``` |
||||
|
||||
See the [documentation][doc] for a complete API specification. |
||||
|
||||
## Development Status |
||||
|
||||
Stable. |
||||
|
||||
--- |
||||
|
||||
Released under the [MIT License](LICENSE.txt). |
||||
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"errors" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
// Marks the test as failed if the error cannot be cast into the provided type
|
||||
// with errors.As.
|
||||
//
|
||||
// assertErrorAsType(t, err, new(ErrFoo))
|
||||
func assertErrorAsType(t *testing.T, err error, typ interface{}, msgAndArgs ...interface{}) bool { |
||||
t.Helper() |
||||
|
||||
return assert.True(t, errors.As(err, typ), msgAndArgs...) |
||||
} |
||||
|
||||
func assertErrorJSONUnmarshalType(t *testing.T, err error, msgAndArgs ...interface{}) bool { |
||||
t.Helper() |
||||
|
||||
return assertErrorAsType(t, err, new(*json.UnmarshalTypeError), msgAndArgs...) |
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
) |
||||
|
||||
// Bool is an atomic type-safe wrapper for bool values.
|
||||
type Bool struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint32 |
||||
} |
||||
|
||||
var _zeroBool bool |
||||
|
||||
// NewBool creates a new Bool.
|
||||
func NewBool(val bool) *Bool { |
||||
x := &Bool{} |
||||
if val != _zeroBool { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped bool.
|
||||
func (x *Bool) Load() bool { |
||||
return truthy(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed bool.
|
||||
func (x *Bool) Store(val bool) { |
||||
x.v.Store(boolToInt(val)) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap for bool values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Bool) CAS(old, new bool) (swapped bool) { |
||||
return x.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for bool values.
|
||||
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) { |
||||
return x.v.CompareAndSwap(boolToInt(old), boolToInt(new)) |
||||
} |
||||
|
||||
// Swap atomically stores the given bool and returns the old
|
||||
// value.
|
||||
func (x *Bool) Swap(val bool) (old bool) { |
||||
return truthy(x.v.Swap(boolToInt(val))) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped bool into JSON.
|
||||
func (x *Bool) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(x.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a bool from JSON.
|
||||
func (x *Bool) UnmarshalJSON(b []byte) error { |
||||
var v bool |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
x.Store(v) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"strconv" |
||||
) |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go
|
||||
|
||||
func truthy(n uint32) bool { |
||||
return n == 1 |
||||
} |
||||
|
||||
func boolToInt(b bool) uint32 { |
||||
if b { |
||||
return 1 |
||||
} |
||||
return 0 |
||||
} |
||||
|
||||
// Toggle atomically negates the Boolean and returns the previous value.
|
||||
func (b *Bool) Toggle() (old bool) { |
||||
for { |
||||
old := b.Load() |
||||
if b.CAS(old, !old) { |
||||
return old |
||||
} |
||||
} |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (b *Bool) String() string { |
||||
return strconv.FormatBool(b.Load()) |
||||
} |
||||
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestBool(t *testing.T) { |
||||
atom := NewBool(false) |
||||
require.False(t, atom.Toggle(), "Expected Toggle to return previous value.") |
||||
require.True(t, atom.Toggle(), "Expected Toggle to return previous value.") |
||||
require.False(t, atom.Toggle(), "Expected Toggle to return previous value.") |
||||
require.True(t, atom.Load(), "Unexpected state after swap.") |
||||
|
||||
require.True(t, atom.CAS(true, true), "CAS should swap when old matches") |
||||
require.True(t, atom.Load(), "CAS should have no effect") |
||||
require.True(t, atom.CAS(true, false), "CAS should swap when old matches") |
||||
require.False(t, atom.Load(), "CAS should have modified the value") |
||||
require.False(t, atom.CAS(true, false), "CAS should fail on old mismatch") |
||||
require.False(t, atom.Load(), "CAS should not have modified the value") |
||||
|
||||
atom.Store(false) |
||||
require.False(t, atom.Load(), "Unexpected state after store.") |
||||
|
||||
prev := atom.Swap(false) |
||||
require.False(t, prev, "Expected Swap to return previous value.") |
||||
|
||||
prev = atom.Swap(true) |
||||
require.False(t, prev, "Expected Swap to return previous value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
atom.Store(true) |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("true"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("false"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.False(t, atom.Load(), "json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("42"), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
t.Run("true", func(t *testing.T) { |
||||
assert.Equal(t, "true", NewBool(true).String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
|
||||
t.Run("false", func(t *testing.T) { |
||||
var b Bool |
||||
assert.Equal(t, "false", b.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func TestBool_InitializeDefaults(t *testing.T) { |
||||
tests := []struct { |
||||
msg string |
||||
newBool func() *Bool |
||||
}{ |
||||
{ |
||||
msg: "Uninitialized", |
||||
newBool: func() *Bool { |
||||
var b Bool |
||||
return &b |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "NewBool with default", |
||||
newBool: func() *Bool { |
||||
return NewBool(false) |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "Bool swapped with default", |
||||
newBool: func() *Bool { |
||||
b := NewBool(true) |
||||
b.Swap(false) |
||||
return b |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "Bool CAS'd with default", |
||||
newBool: func() *Bool { |
||||
b := NewBool(true) |
||||
b.CompareAndSwap(true, false) |
||||
return b |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.msg, func(t *testing.T) { |
||||
t.Run("Marshal", func(t *testing.T) { |
||||
b := tt.newBool() |
||||
marshalled, err := b.MarshalJSON() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, "false", string(marshalled)) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
b := tt.newBool() |
||||
assert.Equal(t, "false", b.String()) |
||||
}) |
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) { |
||||
b := tt.newBool() |
||||
require.True(t, b.CompareAndSwap(false, true)) |
||||
assert.Equal(t, true, b.Load()) |
||||
}) |
||||
|
||||
t.Run("Swap", func(t *testing.T) { |
||||
b := tt.newBool() |
||||
assert.Equal(t, false, b.Swap(true)) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2025 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 |
||||
|
||||
// Bytes is an atomic type-safe wrapper for []byte values.
|
||||
type Bytes struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value |
||||
} |
||||
|
||||
var _zeroBytes []byte |
||||
|
||||
// NewBytes creates a new Bytes.
|
||||
func NewBytes(val []byte) *Bytes { |
||||
x := &Bytes{} |
||||
if val != nil { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped []byte.
|
||||
func (x *Bytes) Load() (b []byte) { |
||||
if x.v.Load() == nil { |
||||
return |
||||
} |
||||
vb := x.v.Load().([]byte) |
||||
b = make([]byte, len(vb)) |
||||
copy(b, vb) |
||||
return |
||||
} |
||||
|
||||
// Store atomically stores the passed []byte.
|
||||
func (x *Bytes) Store(val []byte) { |
||||
b := make([]byte, len(val)) |
||||
copy(b, val) |
||||
x.v.Store(b) |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2020-2025 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 ( |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
) |
||||
|
||||
// MarshalJSON encodes the wrapped []byte as a base64 string.
|
||||
//
|
||||
// This makes it encodable as JSON.
|
||||
func (b *Bytes) MarshalJSON() ([]byte, error) { |
||||
data := b.Load() |
||||
if data == nil { |
||||
return []byte("null"), nil |
||||
} |
||||
encoded := base64.StdEncoding.EncodeToString(data) |
||||
return json.Marshal(encoded) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a base64 string and replaces the wrapped []byte with it.
|
||||
//
|
||||
// This makes it decodable from JSON.
|
||||
func (b *Bytes) UnmarshalJSON(text []byte) error { |
||||
var encoded string |
||||
if err := json.Unmarshal(text, &encoded); err != nil { |
||||
return err |
||||
} |
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(encoded) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
b.Store(decoded) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,252 @@
@@ -0,0 +1,252 @@
|
||||
// Copyright (c) 2020-2025 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 ( |
||||
"encoding/json" |
||||
"runtime" |
||||
"sync" |
||||
"testing" |
||||
"utils.orly" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestBytesNoInitialValue(t *testing.T) { |
||||
atom := NewBytes([]byte{}) |
||||
require.Equal(t, []byte{}, atom.Load(), "Initial value should be empty") |
||||
} |
||||
|
||||
func TestBytes(t *testing.T) { |
||||
atom := NewBytes([]byte{}) |
||||
require.Equal( |
||||
t, []byte{}, atom.Load(), |
||||
"Expected Load to return initialized empty value", |
||||
) |
||||
|
||||
emptyBytes := []byte{} |
||||
atom = NewBytes(emptyBytes) |
||||
require.Equal( |
||||
t, emptyBytes, atom.Load(), |
||||
"Expected Load to return initialized empty value", |
||||
) |
||||
|
||||
testBytes := []byte("test data") |
||||
atom = NewBytes(testBytes) |
||||
loadedBytes := atom.Load() |
||||
require.Equal( |
||||
t, testBytes, loadedBytes, "Expected Load to return initialized value", |
||||
) |
||||
|
||||
// Verify that the returned value is a copy
|
||||
loadedBytes[0] = 'X' |
||||
require.NotEqual( |
||||
t, loadedBytes, atom.Load(), "Load should return a copy of the data", |
||||
) |
||||
|
||||
// Store and verify
|
||||
newBytes := []byte("new data") |
||||
atom.Store(newBytes) |
||||
require.Equal(t, newBytes, atom.Load(), "Unexpected value after Store") |
||||
|
||||
// Modify original data and verify it doesn't affect stored value
|
||||
newBytes[0] = 'X' |
||||
require.NotEqual(t, newBytes, atom.Load(), "Store should copy the data") |
||||
|
||||
t.Run( |
||||
"JSON/Marshal", func(t *testing.T) { |
||||
jsonBytes := []byte("json data") |
||||
atom.Store(jsonBytes) |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal( |
||||
t, []byte(`"anNvbiBkYXRh"`), bytes, |
||||
"json.Marshal should encode as base64", |
||||
) |
||||
}, |
||||
) |
||||
|
||||
t.Run( |
||||
"JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal( |
||||
[]byte(`"dGVzdCBkYXRh"`), &atom, |
||||
) // "test data" in base64
|
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal( |
||||
t, []byte("test data"), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.", |
||||
) |
||||
}, |
||||
) |
||||
|
||||
t.Run( |
||||
"JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("42"), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
}, |
||||
) |
||||
} |
||||
|
||||
func TestBytesConcurrentAccess(t *testing.T) { |
||||
const ( |
||||
parallelism = 4 |
||||
iterations = 1000 |
||||
) |
||||
|
||||
atom := NewBytes([]byte("initial")) |
||||
|
||||
var wg sync.WaitGroup |
||||
wg.Add(parallelism) |
||||
|
||||
// Start multiple goroutines that read and write concurrently
|
||||
for i := 0; i < parallelism; i++ { |
||||
go func(id int) { |
||||
defer wg.Done() |
||||
|
||||
// Each goroutine writes a different value
|
||||
myData := []byte{byte(id)} |
||||
|
||||
for j := 0; j < iterations; j++ { |
||||
// Store our data
|
||||
atom.Store(myData) |
||||
|
||||
// Load the data (which might be from another goroutine)
|
||||
loaded := atom.Load() |
||||
|
||||
// Verify the loaded data is valid (either our data or another goroutine's data)
|
||||
require.LessOrEqual( |
||||
t, len(loaded), parallelism, |
||||
"Loaded data length should not exceed parallelism", |
||||
) |
||||
|
||||
// If it's our data, verify it's correct
|
||||
if len(loaded) == 1 && loaded[0] == byte(id) { |
||||
require.Equal(t, myData, loaded, "Data corruption detected") |
||||
} |
||||
} |
||||
}(i) |
||||
} |
||||
|
||||
wg.Wait() |
||||
} |
||||
|
||||
func TestBytesDataIntegrity(t *testing.T) { |
||||
// Test that large byte slices maintain integrity under concurrent access
|
||||
const ( |
||||
parallelism = 4 |
||||
dataSize = 1024 // 1KB
|
||||
iterations = 100 |
||||
) |
||||
|
||||
// Create test data sets, each with a unique pattern
|
||||
testData := make([][]byte, parallelism) |
||||
for i := 0; i < parallelism; i++ { |
||||
testData[i] = make([]byte, dataSize) |
||||
for j := 0; j < dataSize; j++ { |
||||
testData[i][j] = byte((i + j) % 256) |
||||
} |
||||
} |
||||
|
||||
atom := NewBytes(nil) |
||||
var wg sync.WaitGroup |
||||
wg.Add(parallelism) |
||||
|
||||
for i := 0; i < parallelism; i++ { |
||||
go func(id int) { |
||||
defer wg.Done() |
||||
myData := testData[id] |
||||
|
||||
for j := 0; j < iterations; j++ { |
||||
atom.Store(myData) |
||||
loaded := atom.Load() |
||||
|
||||
// Verify the loaded data is one of our test data sets
|
||||
for k := 0; k < parallelism; k++ { |
||||
if utils.FastEqual(loaded, testData[k]) { |
||||
// Found a match, data is intact
|
||||
break |
||||
} |
||||
if k == parallelism-1 { |
||||
// No match found, data corruption
|
||||
t.Errorf("Data corruption detected: loaded data doesn't match any test set") |
||||
} |
||||
} |
||||
} |
||||
}(i) |
||||
} |
||||
|
||||
wg.Wait() |
||||
} |
||||
|
||||
func TestBytesStress(t *testing.T) { |
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) |
||||
|
||||
atom := NewBytes([]byte("initial")) |
||||
var wg sync.WaitGroup |
||||
|
||||
// We'll run 8 goroutines concurrently
|
||||
workers := 8 |
||||
iterations := 1000 |
||||
wg.Add(workers) |
||||
|
||||
start := make(chan struct{}) |
||||
|
||||
for i := 0; i < workers; i++ { |
||||
go func(id int) { |
||||
defer wg.Done() |
||||
|
||||
// Wait for the start signal
|
||||
<-start |
||||
|
||||
// Each worker gets its own data
|
||||
myData := []byte{byte(id)} |
||||
|
||||
for j := 0; j < iterations; j++ { |
||||
// Alternate between reads and writes
|
||||
if j%2 == 0 { |
||||
atom.Store(myData) |
||||
} else { |
||||
_ = atom.Load() |
||||
} |
||||
} |
||||
}(i) |
||||
} |
||||
|
||||
// Start all goroutines simultaneously
|
||||
close(start) |
||||
wg.Wait() |
||||
} |
||||
|
||||
func BenchmarkBytesParallel(b *testing.B) { |
||||
atom := NewBytes([]byte("benchmark")) |
||||
|
||||
b.RunParallel( |
||||
func(pb *testing.PB) { |
||||
// Each goroutine gets its own data to prevent false sharing
|
||||
myData := []byte("goroutine data") |
||||
|
||||
for pb.Next() { |
||||
atom.Store(myData) |
||||
_ = atom.Load() |
||||
} |
||||
}, |
||||
) |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2020 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 provides simple wrappers around numerics to enforce atomic
|
||||
// access.
|
||||
package atomic |
||||
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"time" |
||||
) |
||||
|
||||
// Duration is an atomic type-safe wrapper for time.Duration values.
|
||||
type Duration struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Int64 |
||||
} |
||||
|
||||
var _zeroDuration time.Duration |
||||
|
||||
// NewDuration creates a new Duration.
|
||||
func NewDuration(val time.Duration) *Duration { |
||||
x := &Duration{} |
||||
if val != _zeroDuration { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped time.Duration.
|
||||
func (x *Duration) Load() time.Duration { |
||||
return time.Duration(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed time.Duration.
|
||||
func (x *Duration) Store(val time.Duration) { |
||||
x.v.Store(int64(val)) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap for time.Duration values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (x *Duration) CAS(old, new time.Duration) (swapped bool) { |
||||
return x.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for time.Duration values.
|
||||
func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) { |
||||
return x.v.CompareAndSwap(int64(old), int64(new)) |
||||
} |
||||
|
||||
// Swap atomically stores the given time.Duration and returns the old
|
||||
// value.
|
||||
func (x *Duration) Swap(val time.Duration) (old time.Duration) { |
||||
return time.Duration(x.v.Swap(int64(val))) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped time.Duration into JSON.
|
||||
func (x *Duration) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(x.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a time.Duration from JSON.
|
||||
func (x *Duration) UnmarshalJSON(b []byte) error { |
||||
var v time.Duration |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
x.Store(v) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 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 "time" |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
|
||||
|
||||
// Add atomically adds to the wrapped time.Duration and returns the new value.
|
||||
func (x *Duration) Add(delta time.Duration) time.Duration { |
||||
return time.Duration(x.v.Add(int64(delta))) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
|
||||
func (x *Duration) Sub(delta time.Duration) time.Duration { |
||||
return time.Duration(x.v.Sub(int64(delta))) |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (x *Duration) String() string { |
||||
return x.Load().String() |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestDuration(t *testing.T) { |
||||
atom := NewDuration(5 * time.Minute) |
||||
|
||||
require.Equal(t, 5*time.Minute, atom.Load(), "Load didn't work.") |
||||
require.Equal(t, 6*time.Minute, atom.Add(time.Minute), "Add didn't work.") |
||||
require.Equal(t, 4*time.Minute, atom.Sub(2*time.Minute), "Sub didn't work.") |
||||
|
||||
require.True(t, atom.CAS(4*time.Minute, time.Minute), "CAS didn't report a swap.") |
||||
require.Equal(t, time.Minute, atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, time.Minute, atom.Swap(2*time.Minute), "Swap didn't return the old value.") |
||||
require.Equal(t, 2*time.Minute, atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(10 * time.Minute) |
||||
require.Equal(t, 10*time.Minute, atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
atom.Store(time.Second) |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("1000000000"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("1000000000"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, time.Second, atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("\"1000000000\""), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
assert.Equal(t, "42s", NewDuration(42*time.Second).String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 for error values.
|
||||
type Error struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value |
||||
} |
||||
|
||||
var _zeroError error |
||||
|
||||
// NewError creates a new Error.
|
||||
func NewError(val error) *Error { |
||||
x := &Error{} |
||||
if val != _zeroError { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped error.
|
||||
func (x *Error) Load() error { |
||||
return unpackError(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed error.
|
||||
func (x *Error) Store(val error) { |
||||
x.v.Store(packError(val)) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for error values.
|
||||
func (x *Error) CompareAndSwap(old, new error) (swapped bool) { |
||||
if x.v.CompareAndSwap(packError(old), packError(new)) { |
||||
return true |
||||
} |
||||
|
||||
if old == _zeroError { |
||||
// If the old value is the empty value, then it's possible the
|
||||
// underlying Value hasn't been set and is nil, so retry with nil.
|
||||
return x.v.CompareAndSwap(nil, packError(new)) |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
// Swap atomically stores the given error and returns the old
|
||||
// value.
|
||||
func (x *Error) Swap(val error) (old error) { |
||||
return unpackError(x.v.Swap(packError(val))) |
||||
} |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2020-2022 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 |
||||
|
||||
// atomic.Value panics on nil inputs, or if the underlying type changes.
|
||||
// Stabilize by always storing a custom struct that we control.
|
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -compareandswap -swap -file=error.go
|
||||
|
||||
type packedError struct{ Value error } |
||||
|
||||
func packError(v error) interface{} { |
||||
return packedError{v} |
||||
} |
||||
|
||||
func unpackError(v interface{}) error { |
||||
if err, ok := v.(packedError); ok { |
||||
return err.Value |
||||
} |
||||
return nil |
||||
} |
||||
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
// 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/assert" |
||||
"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") |
||||
} |
||||
|
||||
func TestErrorSwap(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") |
||||
|
||||
old := atom.Swap(err2) |
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value") |
||||
require.Equal(t, err1, old, "Expected old to be initial value") |
||||
} |
||||
|
||||
func TestErrorCompareAndSwap(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") |
||||
|
||||
swapped := atom.CompareAndSwap(err2, err2) |
||||
require.False(t, swapped, "Expected swapped to be false") |
||||
require.Equal(t, err1, atom.Load(), "Expected Load to return initial value") |
||||
|
||||
swapped = atom.CompareAndSwap(err1, err2) |
||||
require.True(t, swapped, "Expected swapped to be true") |
||||
require.Equal(t, err2, atom.Load(), "Expected Load to return overridden value") |
||||
} |
||||
|
||||
func TestError_InitializeDefaults(t *testing.T) { |
||||
tests := []struct { |
||||
msg string |
||||
newError func() *Error |
||||
}{ |
||||
{ |
||||
msg: "Uninitialized", |
||||
newError: func() *Error { |
||||
var e Error |
||||
return &e |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "NewError with default", |
||||
newError: func() *Error { |
||||
return NewError(nil) |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "Error swapped with default", |
||||
newError: func() *Error { |
||||
e := NewError(assert.AnError) |
||||
e.Swap(nil) |
||||
return e |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "Error CAS'd with default", |
||||
newError: func() *Error { |
||||
e := NewError(assert.AnError) |
||||
e.CompareAndSwap(assert.AnError, nil) |
||||
return e |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.msg, func(t *testing.T) { |
||||
t.Run("CompareAndSwap", func(t *testing.T) { |
||||
e := tt.newError() |
||||
require.True(t, e.CompareAndSwap(nil, assert.AnError)) |
||||
assert.Equal(t, assert.AnError, e.Load()) |
||||
}) |
||||
|
||||
t.Run("Swap", func(t *testing.T) { |
||||
e := tt.newError() |
||||
assert.Equal(t, nil, e.Swap(assert.AnError)) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
// 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_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"utils.orly/atomic" |
||||
) |
||||
|
||||
func Example() { |
||||
// Uint32 is a thin wrapper around the primitive uint32 type.
|
||||
var atom atomic.Uint32 |
||||
|
||||
// The wrapper ensures that all operations are atomic.
|
||||
atom.Store(42) |
||||
fmt.Println(atom.Inc()) |
||||
fmt.Println(atom.CompareAndSwap(43, 0)) |
||||
fmt.Println(atom.Load()) |
||||
|
||||
// Output:
|
||||
// 43
|
||||
// true
|
||||
// 0
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
) |
||||
|
||||
// Float32 is an atomic type-safe wrapper for float32 values.
|
||||
type Float32 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint32 |
||||
} |
||||
|
||||
var _zeroFloat32 float32 |
||||
|
||||
// NewFloat32 creates a new Float32.
|
||||
func NewFloat32(val float32) *Float32 { |
||||
x := &Float32{} |
||||
if val != _zeroFloat32 { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped float32.
|
||||
func (x *Float32) Load() float32 { |
||||
return math.Float32frombits(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed float32.
|
||||
func (x *Float32) Store(val float32) { |
||||
x.v.Store(math.Float32bits(val)) |
||||
} |
||||
|
||||
// Swap atomically stores the given float32 and returns the old
|
||||
// value.
|
||||
func (x *Float32) Swap(val float32) (old float32) { |
||||
return math.Float32frombits(x.v.Swap(math.Float32bits(val))) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped float32 into JSON.
|
||||
func (x *Float32) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(x.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a float32 from JSON.
|
||||
func (x *Float32) UnmarshalJSON(b []byte) error { |
||||
var v float32 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
x.Store(v) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2020-2022 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 ( |
||||
"math" |
||||
"strconv" |
||||
) |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Float32 -type=float32 -wrapped=Uint32 -pack=math.Float32bits -unpack=math.Float32frombits -swap -json -imports math -file=float32.go
|
||||
|
||||
// Add atomically adds to the wrapped float32 and returns the new value.
|
||||
func (f *Float32) Add(delta float32) float32 { |
||||
for { |
||||
old := f.Load() |
||||
new := old + delta |
||||
if f.CAS(old, new) { |
||||
return new |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped float32 and returns the new value.
|
||||
func (f *Float32) Sub(delta float32) float32 { |
||||
return f.Add(-delta) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float32) CAS(old, new float32) (swapped bool) { |
||||
return f.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float32 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float32) CompareAndSwap(old, new float32) (swapped bool) { |
||||
return f.v.CompareAndSwap(math.Float32bits(old), math.Float32bits(new)) |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (f *Float32) String() string { |
||||
// 'g' is the behavior for floats with %v.
|
||||
return strconv.FormatFloat(float64(f.Load()), 'g', -1, 32) |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestFloat32(t *testing.T) { |
||||
atom := NewFloat32(4.2) |
||||
|
||||
require.Equal(t, float32(4.2), atom.Load(), "Load didn't work.") |
||||
|
||||
require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.") |
||||
require.Equal(t, float32(0.5), atom.Load(), "CAS didn't set the correct value.") |
||||
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.") |
||||
|
||||
atom.Store(42.0) |
||||
require.Equal(t, float32(42.0), atom.Load(), "Store didn't set the correct value.") |
||||
require.Equal(t, float32(42.5), atom.Add(0.5), "Add didn't work.") |
||||
require.Equal(t, float32(42.0), atom.Sub(0.5), "Sub didn't work.") |
||||
|
||||
require.Equal(t, float32(42.0), atom.Swap(45.0), "Swap didn't return the old value.") |
||||
require.Equal(t, float32(45.0), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
atom.Store(42.5) |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42.5"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40.5"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, float32(40.5), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("\"40.5\""), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
assert.Equal(t, "42.5", NewFloat32(42.5).String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
) |
||||
|
||||
// Float64 is an atomic type-safe wrapper for float64 values.
|
||||
type Float64 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Uint64 |
||||
} |
||||
|
||||
var _zeroFloat64 float64 |
||||
|
||||
// NewFloat64 creates a new Float64.
|
||||
func NewFloat64(val float64) *Float64 { |
||||
x := &Float64{} |
||||
if val != _zeroFloat64 { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped float64.
|
||||
func (x *Float64) Load() float64 { |
||||
return math.Float64frombits(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed float64.
|
||||
func (x *Float64) Store(val float64) { |
||||
x.v.Store(math.Float64bits(val)) |
||||
} |
||||
|
||||
// Swap atomically stores the given float64 and returns the old
|
||||
// value.
|
||||
func (x *Float64) Swap(val float64) (old float64) { |
||||
return math.Float64frombits(x.v.Swap(math.Float64bits(val))) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped float64 into JSON.
|
||||
func (x *Float64) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(x.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a float64 from JSON.
|
||||
func (x *Float64) UnmarshalJSON(b []byte) error { |
||||
var v float64 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
x.Store(v) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2020-2022 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 ( |
||||
"math" |
||||
"strconv" |
||||
) |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -swap -json -imports math -file=float64.go
|
||||
|
||||
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Add(delta float64) float64 { |
||||
for { |
||||
old := f.Load() |
||||
new := old + delta |
||||
if f.CAS(old, new) { |
||||
return new |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Sub(delta float64) float64 { |
||||
return f.Add(-delta) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (f *Float64) CAS(old, new float64) (swapped bool) { |
||||
return f.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for float64 values.
|
||||
//
|
||||
// Note: CompareAndSwap handles NaN incorrectly. NaN != NaN using Go's inbuilt operators
|
||||
// but CompareAndSwap allows a stored NaN to compare equal to a passed in NaN.
|
||||
// This avoids typical CompareAndSwap loops from blocking forever, e.g.,
|
||||
//
|
||||
// for {
|
||||
// old := atom.Load()
|
||||
// new = f(old)
|
||||
// if atom.CompareAndSwap(old, new) {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// If CompareAndSwap did not match NaN to match, then the above would loop forever.
|
||||
func (f *Float64) CompareAndSwap(old, new float64) (swapped bool) { |
||||
return f.v.CompareAndSwap(math.Float64bits(old), math.Float64bits(new)) |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (f *Float64) String() string { |
||||
// 'g' is the behavior for floats with %v.
|
||||
return strconv.FormatFloat(f.Load(), 'g', -1, 64) |
||||
} |
||||
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestFloat64(t *testing.T) { |
||||
atom := NewFloat64(4.2) |
||||
|
||||
require.Equal(t, float64(4.2), atom.Load(), "Load didn't work.") |
||||
|
||||
require.True(t, atom.CAS(4.2, 0.5), "CAS didn't report a swap.") |
||||
require.Equal(t, float64(0.5), atom.Load(), "CAS didn't set the correct value.") |
||||
require.False(t, atom.CAS(0.0, 1.5), "CAS reported a swap.") |
||||
|
||||
atom.Store(42.0) |
||||
require.Equal(t, float64(42.0), atom.Load(), "Store didn't set the correct value.") |
||||
require.Equal(t, float64(42.5), atom.Add(0.5), "Add didn't work.") |
||||
require.Equal(t, float64(42.0), atom.Sub(0.5), "Sub didn't work.") |
||||
|
||||
require.Equal(t, float64(42.0), atom.Swap(45.0), "Swap didn't return the old value.") |
||||
require.Equal(t, float64(45.0), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
atom.Store(42.5) |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42.5"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40.5"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, float64(40.5), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("\"40.5\""), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
assert.Equal(t, "42.5", NewFloat64(42.5).String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2020 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 |
||||
|
||||
//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
|
||||
//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
|
||||
//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
|
||||
//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
|
||||
//go:generate bin/gen-atomicint -name=Uintptr -wrapped=uintptr -unsigned -file=uintptr.go
|
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Int32 is an atomic wrapper around int32.
|
||||
type Int32 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v int32 |
||||
} |
||||
|
||||
// NewInt32 creates a new Int32.
|
||||
func NewInt32(val int32) *Int32 { |
||||
return &Int32{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int32) Load() int32 { |
||||
return atomic.LoadInt32(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Add(delta int32) int32 { |
||||
return atomic.AddInt32(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Sub(delta int32) int32 { |
||||
return atomic.AddInt32(&i.v, -delta) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Inc() int32 { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Dec() int32 { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int32) CAS(old, new int32) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int32) CompareAndSwap(old, new int32) (swapped bool) { |
||||
return atomic.CompareAndSwapInt32(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int32) Store(val int32) { |
||||
atomic.StoreInt32(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||
func (i *Int32) Swap(val int32) (old int32) { |
||||
return atomic.SwapInt32(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped int32 into JSON.
|
||||
func (i *Int32) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped int32.
|
||||
func (i *Int32) UnmarshalJSON(b []byte) error { |
||||
var v int32 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Int32) String() string { |
||||
v := i.Load() |
||||
return strconv.FormatInt(int64(v), 10) |
||||
} |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestInt32(t *testing.T) { |
||||
atom := NewInt32(42) |
||||
|
||||
require.Equal(t, int32(42), atom.Load(), "Load didn't work.") |
||||
require.Equal(t, int32(46), atom.Add(4), "Add didn't work.") |
||||
require.Equal(t, int32(44), atom.Sub(2), "Sub didn't work.") |
||||
require.Equal(t, int32(45), atom.Inc(), "Inc didn't work.") |
||||
require.Equal(t, int32(44), atom.Dec(), "Dec didn't work.") |
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") |
||||
require.Equal(t, int32(0), atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, int32(0), atom.Swap(1), "Swap didn't return the old value.") |
||||
require.Equal(t, int32(1), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(42) |
||||
require.Equal(t, int32(42), atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, int32(40), atom.Load(), "json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"40"`), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
t.Run("positive", func(t *testing.T) { |
||||
atom := NewInt32(math.MaxInt32) |
||||
assert.Equal(t, "2147483647", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
|
||||
t.Run("negative", func(t *testing.T) { |
||||
atom := NewInt32(math.MinInt32) |
||||
assert.Equal(t, "-2147483648", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
}) |
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Int64 is an atomic wrapper around int64.
|
||||
type Int64 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v int64 |
||||
} |
||||
|
||||
// NewInt64 creates a new Int64.
|
||||
func NewInt64(val int64) *Int64 { |
||||
return &Int64{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int64) Load() int64 { |
||||
return atomic.LoadInt64(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Add(delta int64) int64 { |
||||
return atomic.AddInt64(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Sub(delta int64) int64 { |
||||
return atomic.AddInt64(&i.v, -delta) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Inc() int64 { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Dec() int64 { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Int64) CAS(old, new int64) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Int64) CompareAndSwap(old, new int64) (swapped bool) { |
||||
return atomic.CompareAndSwapInt64(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int64) Store(val int64) { |
||||
atomic.StoreInt64(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||
func (i *Int64) Swap(val int64) (old int64) { |
||||
return atomic.SwapInt64(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped int64 into JSON.
|
||||
func (i *Int64) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped int64.
|
||||
func (i *Int64) UnmarshalJSON(b []byte) error { |
||||
var v int64 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Int64) String() string { |
||||
v := i.Load() |
||||
return strconv.FormatInt(int64(v), 10) |
||||
} |
||||
@ -0,0 +1,82 @@
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestInt64(t *testing.T) { |
||||
atom := NewInt64(42) |
||||
|
||||
require.Equal(t, int64(42), atom.Load(), "Load didn't work.") |
||||
require.Equal(t, int64(46), atom.Add(4), "Add didn't work.") |
||||
require.Equal(t, int64(44), atom.Sub(2), "Sub didn't work.") |
||||
require.Equal(t, int64(45), atom.Inc(), "Inc didn't work.") |
||||
require.Equal(t, int64(44), atom.Dec(), "Dec didn't work.") |
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") |
||||
require.Equal(t, int64(0), atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, int64(0), atom.Swap(1), "Swap didn't return the old value.") |
||||
require.Equal(t, int64(1), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(42) |
||||
require.Equal(t, int64(42), atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, int64(40), atom.Load(), "json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"40"`), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
t.Run("positive", func(t *testing.T) { |
||||
atom := NewInt64(math.MaxInt64) |
||||
assert.Equal(t, "9223372036854775807", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
|
||||
t.Run("negative", func(t *testing.T) { |
||||
atom := NewInt64(math.MinInt64) |
||||
assert.Equal(t, "-9223372036854775808", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
}) |
||||
} |
||||
@ -0,0 +1,116 @@
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2020-2022 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.
|
||||
|
||||
// gen-atomicint generates an atomic wrapper around an integer type.
|
||||
//
|
||||
// gen-atomicint -name Int32 -wrapped int32 -file out.go
|
||||
//
|
||||
// The generated wrapper will use the functions in the sync/atomic package
|
||||
// named after the generated type.
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"embed" |
||||
"errors" |
||||
"flag" |
||||
"fmt" |
||||
"go/format" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"text/template" |
||||
"time" |
||||
) |
||||
|
||||
func main() { |
||||
log.SetFlags(0) |
||||
if err := run(os.Args[1:]); err != nil { |
||||
log.Fatalf("%+v", err) |
||||
} |
||||
} |
||||
|
||||
func run(args []string) error { |
||||
var opts struct { |
||||
Name string |
||||
Wrapped string |
||||
File string |
||||
Unsigned bool |
||||
} |
||||
|
||||
flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError) |
||||
|
||||
flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)") |
||||
flag.StringVar(&opts.Wrapped, "wrapped", "", "name of the wrapped type (e.g. int32)") |
||||
flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)") |
||||
flag.BoolVar(&opts.Unsigned, "unsigned", false, "whether the type is unsigned") |
||||
|
||||
if err := flag.Parse(args); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(opts.Name) == 0 || len(opts.Wrapped) == 0 { |
||||
return errors.New("flags -name and -wrapped are required") |
||||
} |
||||
|
||||
var w io.Writer = os.Stdout |
||||
if file := opts.File; len(file) > 0 { |
||||
f, err := os.Create(file) |
||||
if err != nil { |
||||
return fmt.Errorf("create %q: %v", file, err) |
||||
} |
||||
defer f.Close() |
||||
|
||||
w = f |
||||
} |
||||
|
||||
data := struct { |
||||
Name string |
||||
Wrapped string |
||||
Unsigned bool |
||||
ToYear int |
||||
}{ |
||||
Name: opts.Name, |
||||
Wrapped: opts.Wrapped, |
||||
Unsigned: opts.Unsigned, |
||||
ToYear: time.Now().Year(), |
||||
} |
||||
|
||||
var buff bytes.Buffer |
||||
if err := _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", data); err != nil { |
||||
return fmt.Errorf("render template: %v", err) |
||||
} |
||||
|
||||
bs, err := format.Source(buff.Bytes()) |
||||
if err != nil { |
||||
return fmt.Errorf("reformat source: %v", err) |
||||
} |
||||
|
||||
io.WriteString(w, "// @generated Code generated by gen-atomicint.\n\n") |
||||
_, err = w.Write(bs) |
||||
return err |
||||
} |
||||
|
||||
var ( |
||||
//go:embed *.tmpl
|
||||
_tmplFS embed.FS |
||||
|
||||
_tmpl = template.Must(template.New("atomicint").ParseFS(_tmplFS, "*.tmpl")) |
||||
) |
||||
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) 2020-{{.ToYear}} 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// {{ .Name }} is an atomic wrapper around {{ .Wrapped }}. |
||||
type {{ .Name }} struct { |
||||
_ nocmp // disallow non-atomic comparison |
||||
|
||||
v {{ .Wrapped }} |
||||
} |
||||
|
||||
// New{{ .Name }} creates a new {{ .Name }}. |
||||
func New{{ .Name }}(val {{ .Wrapped }}) *{{ .Name }} { |
||||
return &{{ .Name }}{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value. |
||||
func (i *{{ .Name }}) Load() {{ .Wrapped }} { |
||||
return atomic.Load{{ .Name }}(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped {{ .Wrapped }} and returns the new value. |
||||
func (i *{{ .Name }}) Add(delta {{ .Wrapped }}) {{ .Wrapped }} { |
||||
return atomic.Add{{ .Name }}(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped {{ .Wrapped }} and returns the new value. |
||||
func (i *{{ .Name }}) Sub(delta {{ .Wrapped }}) {{ .Wrapped }} { |
||||
return atomic.Add{{ .Name }}(&i.v, |
||||
{{- if .Unsigned -}} |
||||
^(delta - 1) |
||||
{{- else -}} |
||||
-delta |
||||
{{- end -}} |
||||
) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped {{ .Wrapped }} and returns the new value. |
||||
func (i *{{ .Name }}) Inc() {{ .Wrapped }} { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped {{ .Wrapped }} and returns the new value. |
||||
func (i *{{ .Name }}) Dec() {{ .Wrapped }} { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap. |
||||
// |
||||
// Deprecated: Use CompareAndSwap. |
||||
func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap. |
||||
func (i *{{ .Name }}) CompareAndSwap(old, new {{ .Wrapped }}) (swapped bool) { |
||||
return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value. |
||||
func (i *{{ .Name }}) Store(val {{ .Wrapped }}) { |
||||
atomic.Store{{ .Name }}(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped {{ .Wrapped }} and returns the old value. |
||||
func (i *{{ .Name }}) Swap(val {{ .Wrapped }}) (old {{ .Wrapped }}) { |
||||
return atomic.Swap{{ .Name }}(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped {{ .Wrapped }} into JSON. |
||||
func (i *{{ .Name }}) MarshalJSON() (by, er) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped {{ .Wrapped }}. |
||||
func (i *{{ .Name }}) UnmarshalJSON(b by) er { |
||||
var v {{ .Wrapped }} |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string. |
||||
func (i *{{ .Name }}) String() string { |
||||
v := i.Load() |
||||
{{ if .Unsigned -}} |
||||
return strconv.FormatUint(uint64(v), 10) |
||||
{{- else -}} |
||||
return strconv.FormatInt(int64(v), 10) |
||||
{{- end }} |
||||
} |
||||
@ -0,0 +1,203 @@
@@ -0,0 +1,203 @@
|
||||
// Copyright (c) 2020-2022 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.
|
||||
|
||||
// gen-atomicwrapper generates wrapper types around other atomic types.
|
||||
//
|
||||
// It supports plugging in functions which convert the value inside the atomic
|
||||
// type to the user-facing value. For example,
|
||||
//
|
||||
// Given, atomic.Value and the functions,
|
||||
//
|
||||
// func packString(string) enveloper{}
|
||||
// func unpackString(enveloper{}) string
|
||||
//
|
||||
// We can run the following command:
|
||||
//
|
||||
// gen-atomicwrapper -name String -wrapped Value \
|
||||
// -type string -pack fromString -unpack tostring
|
||||
//
|
||||
// This wil generate approximately,
|
||||
//
|
||||
// type String struct{ v Value }
|
||||
//
|
||||
// func (s *String) Load() string {
|
||||
// return unpackString(v.Load())
|
||||
// }
|
||||
//
|
||||
// func (s *String) Store(s string) {
|
||||
// return s.v.Store(packString(s))
|
||||
// }
|
||||
//
|
||||
// The packing/unpacking logic allows the stored value to be different from
|
||||
// the user-facing value.
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"embed" |
||||
"errors" |
||||
"flag" |
||||
"fmt" |
||||
"go/format" |
||||
"io" |
||||
"log" |
||||
"os" |
||||
"sort" |
||||
"strings" |
||||
"text/template" |
||||
"time" |
||||
) |
||||
|
||||
func main() { |
||||
log.SetFlags(0) |
||||
if err := run(os.Args[1:]); err != nil { |
||||
log.Fatalf("%+v", err) |
||||
} |
||||
} |
||||
|
||||
type stringList []string |
||||
|
||||
func (sl *stringList) String() string { |
||||
return strings.Join(*sl, ",") |
||||
} |
||||
|
||||
func (sl *stringList) Set(s string) error { |
||||
for _, i := range strings.Split(s, ",") { |
||||
*sl = append(*sl, strings.TrimSpace(i)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func run(args []string) error { |
||||
var opts struct { |
||||
Name string |
||||
Wrapped string |
||||
Type string |
||||
|
||||
Imports stringList |
||||
Pack, Unpack string |
||||
|
||||
CAS bool |
||||
CompareAndSwap bool |
||||
Swap bool |
||||
JSON bool |
||||
|
||||
File string |
||||
ToYear int |
||||
} |
||||
|
||||
opts.ToYear = time.Now().Year() |
||||
|
||||
fl := flag.NewFlagSet("gen-atomicwrapper", flag.ContinueOnError) |
||||
|
||||
// Required flags
|
||||
fl.StringVar(&opts.Name, "name", "", |
||||
"name of the generated type (e.g. Duration)") |
||||
fl.StringVar(&opts.Wrapped, "wrapped", "", |
||||
"name of the wrapped atomic (e.g. Int64)") |
||||
fl.StringVar(&opts.Type, "type", "", |
||||
"name of the type exposed by the atomic (e.g. time.Duration)") |
||||
|
||||
// Optional flags
|
||||
fl.Var(&opts.Imports, "imports", |
||||
"comma separated list of imports to add") |
||||
fl.StringVar(&opts.Pack, "pack", "", |
||||
"function to transform values with before storage") |
||||
fl.StringVar(&opts.Unpack, "unpack", "", |
||||
"function to reverse packing on loading") |
||||
fl.StringVar(&opts.File, "file", "", |
||||
"output file path (default: stdout)") |
||||
|
||||
// Switches for individual methods. Underlying atomics must support
|
||||
// these.
|
||||
fl.BoolVar(&opts.CAS, "cas", false, |
||||
"generate a deprecated `CAS(old, new) bool` method; requires -pack") |
||||
fl.BoolVar(&opts.CompareAndSwap, "compareandswap", false, |
||||
"generate a `CompareAndSwap(old, new) bool` method; requires -pack") |
||||
fl.BoolVar(&opts.Swap, "swap", false, |
||||
"generate a `Swap(new) old` method; requires -pack and -unpack") |
||||
fl.BoolVar(&opts.JSON, "json", false, |
||||
"generate `Marshal/UnmarshJSON` methods") |
||||
|
||||
if err := fl.Parse(args); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if len(opts.Name) == 0 || |
||||
len(opts.Wrapped) == 0 || |
||||
len(opts.Type) == 0 || |
||||
len(opts.Pack) == 0 || |
||||
len(opts.Unpack) == 0 { |
||||
return errors.New("flags -name, -wrapped, -pack, -unpack and -type are required") |
||||
} |
||||
|
||||
if opts.CAS { |
||||
opts.CompareAndSwap = true |
||||
} |
||||
|
||||
var w io.Writer = os.Stdout |
||||
if file := opts.File; len(file) > 0 { |
||||
f, err := os.Create(file) |
||||
if err != nil { |
||||
return fmt.Errorf("create %q: %v", file, err) |
||||
} |
||||
defer f.Close() |
||||
|
||||
w = f |
||||
} |
||||
|
||||
// Import encoding/json if needed.
|
||||
if opts.JSON { |
||||
found := false |
||||
for _, imp := range opts.Imports { |
||||
if imp == "encoding/json" { |
||||
found = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
if !found { |
||||
opts.Imports = append(opts.Imports, "encoding/json") |
||||
} |
||||
} |
||||
|
||||
sort.Strings(opts.Imports) |
||||
|
||||
var buff bytes.Buffer |
||||
if err := _tmpl.ExecuteTemplate(&buff, "wrapper.tmpl", opts); err != nil { |
||||
return fmt.Errorf("render template: %v", err) |
||||
} |
||||
|
||||
bs, err := format.Source(buff.Bytes()) |
||||
if err != nil { |
||||
return fmt.Errorf("reformat source: %v", err) |
||||
} |
||||
|
||||
io.WriteString(w, "// @generated Code generated by gen-atomicwrapper.\n\n") |
||||
_, err = w.Write(bs) |
||||
return err |
||||
} |
||||
|
||||
var ( |
||||
//go:embed *.tmpl
|
||||
_tmplFS embed.FS |
||||
|
||||
_tmpl = template.Must(template.New("atomicwrapper").ParseFS(_tmplFS, "*.tmpl")) |
||||
) |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2020-{{.ToYear}} 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 |
||||
|
||||
{{ with .Imports }} |
||||
import ( |
||||
{{ range . -}} |
||||
{{ printf "%q" . }} |
||||
{{ end }} |
||||
) |
||||
{{ end }} |
||||
|
||||
// {{ .Name }} is an atomic type-safe wrapper for {{ .Type }} values. |
||||
type {{ .Name }} struct{ |
||||
_ nocmp // disallow non-atomic comparison |
||||
|
||||
v {{ .Wrapped }} |
||||
} |
||||
|
||||
var _zero{{ .Name }} {{ .Type }} |
||||
|
||||
|
||||
// New{{ .Name }} creates a new {{ .Name }}. |
||||
func New{{ .Name }}(val {{ .Type }}) *{{ .Name }} { |
||||
x := &{{ .Name }}{} |
||||
if val != _zero{{ .Name }} { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped {{ .Type }}. |
||||
func (x *{{ .Name }}) Load() {{ .Type }} { |
||||
{{ if .Unpack -}} |
||||
return {{ .Unpack }}(x.v.Load()) |
||||
{{- else -}} |
||||
if v := x.v.Load(); v != nil { |
||||
return v.({{ .Type }}) |
||||
} |
||||
return _zero{{ .Name }} |
||||
{{- end }} |
||||
} |
||||
|
||||
// Store atomically stores the passed {{ .Type }}. |
||||
func (x *{{ .Name }}) Store(val {{ .Type }}) { |
||||
x.v.Store({{ .Pack }}(val)) |
||||
} |
||||
|
||||
{{ if .CAS -}} |
||||
// CAS is an atomic compare-and-swap for {{ .Type }} values. |
||||
// |
||||
// Deprecated: Use CompareAndSwap. |
||||
func (x *{{ .Name }}) CAS(old, new {{ .Type }}) (swapped bool) { |
||||
return x.CompareAndSwap(old, new) |
||||
} |
||||
{{- end }} |
||||
|
||||
{{ if .CompareAndSwap -}} |
||||
// CompareAndSwap is an atomic compare-and-swap for {{ .Type }} values. |
||||
func (x *{{ .Name }}) CompareAndSwap(old, new {{ .Type }}) (swapped bool) { |
||||
{{ if eq .Wrapped "Value" -}} |
||||
if x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new)) { |
||||
return true |
||||
} |
||||
|
||||
if old == _zero{{ .Name }} { |
||||
// If the old value is the empty value, then it's possible the |
||||
// underlying Value hasn't been set and is nil, so retry with nil. |
||||
return x.v.CompareAndSwap(nil, {{ .Pack }}(new)) |
||||
} |
||||
|
||||
return false |
||||
{{- else -}} |
||||
return x.v.CompareAndSwap({{ .Pack }}(old), {{ .Pack }}(new)) |
||||
{{- end }} |
||||
} |
||||
{{- end }} |
||||
|
||||
{{ if .Swap -}} |
||||
// Swap atomically stores the given {{ .Type }} and returns the old |
||||
// value. |
||||
func (x *{{ .Name }}) Swap(val {{ .Type }}) (old {{ .Type }}) { |
||||
return {{ .Unpack }}(x.v.Swap({{ .Pack }}(val))) |
||||
} |
||||
{{- end }} |
||||
|
||||
{{ if .JSON -}} |
||||
// MarshalJSON encodes the wrapped {{ .Type }} into JSON. |
||||
func (x *{{ .Name }}) MarshalJSON() (by, er) { |
||||
return json.Marshal(x.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes a {{ .Type }} from JSON. |
||||
func (x *{{ .Name }}) UnmarshalJSON(b by) er { |
||||
var v {{ .Type }} |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
x.Store(v) |
||||
return nil |
||||
} |
||||
{{- end }} |
||||
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2020 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 |
||||
|
||||
// nocmp is an uncomparable struct. Embed this inside another struct to make
|
||||
// it uncomparable.
|
||||
//
|
||||
// type Foo struct {
|
||||
// nocmp
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// This DOES NOT:
|
||||
//
|
||||
// - Disallow shallow copies of structs
|
||||
// - Disallow comparison of pointers to uncomparable structs
|
||||
type nocmp [0]func() |
||||
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"bytes" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"reflect" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestNocmpComparability(t *testing.T) { |
||||
tests := []struct { |
||||
desc string |
||||
give interface{} |
||||
comparable bool |
||||
}{ |
||||
{ |
||||
desc: "nocmp struct", |
||||
give: nocmp{}, |
||||
}, |
||||
{ |
||||
desc: "struct with nocmp embedded", |
||||
give: struct{ nocmp }{}, |
||||
}, |
||||
{ |
||||
desc: "pointer to struct with nocmp embedded", |
||||
give: &struct{ nocmp }{}, |
||||
comparable: true, |
||||
}, |
||||
|
||||
// All exported types must be uncomparable.
|
||||
{desc: "Bool", give: Bool{}}, |
||||
{desc: "Duration", give: Duration{}}, |
||||
{desc: "Error", give: Error{}}, |
||||
{desc: "Float64", give: Float64{}}, |
||||
{desc: "Int32", give: Int32{}}, |
||||
{desc: "Int64", give: Int64{}}, |
||||
{desc: "String", give: String{}}, |
||||
{desc: "Uint32", give: Uint32{}}, |
||||
{desc: "Uint64", give: Uint64{}}, |
||||
{desc: "Value", give: Value{}}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
typ := reflect.TypeOf(tt.give) |
||||
assert.Equalf(t, tt.comparable, typ.Comparable(), |
||||
"type %v comparablity mismatch", typ) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// nocmp must not add to the size of a struct in-memory.
|
||||
func TestNocmpSize(t *testing.T) { |
||||
type x struct{ _ int } |
||||
|
||||
before := reflect.TypeOf(x{}).Size() |
||||
|
||||
type y struct { |
||||
_ nocmp |
||||
_ x |
||||
} |
||||
|
||||
after := reflect.TypeOf(y{}).Size() |
||||
|
||||
assert.Equal(t, before, after, |
||||
"expected nocmp to have no effect on struct size") |
||||
} |
||||
|
||||
// This test will fail to compile if we disallow copying of nocmp.
|
||||
//
|
||||
// We need to allow this so that users can do,
|
||||
//
|
||||
// var x atomic.Int32
|
||||
// x = atomic.NewInt32(1)
|
||||
func TestNocmpCopy(t *testing.T) { |
||||
type foo struct{ _ nocmp } |
||||
|
||||
t.Run("struct copy", func(t *testing.T) { |
||||
a := foo{} |
||||
b := a |
||||
_ = b // unused
|
||||
}) |
||||
|
||||
t.Run("pointer copy", func(t *testing.T) { |
||||
a := &foo{} |
||||
b := *a |
||||
_ = b // unused
|
||||
}) |
||||
} |
||||
|
||||
// Fake go.mod with no dependencies.
|
||||
const _exampleGoMod = `module example.com/nocmp` |
||||
|
||||
const _badFile = `package atomic |
||||
|
||||
import "fmt" |
||||
|
||||
type Int64 struct { |
||||
nocmp |
||||
|
||||
v int64 |
||||
} |
||||
|
||||
func shouldNotCompile() { |
||||
var x, y Int64 |
||||
fmt.Println(x == y) |
||||
} |
||||
` |
||||
|
||||
func TestNocmpIntegration(t *testing.T) { |
||||
tempdir := t.TempDir() |
||||
|
||||
nocmp, err := os.ReadFile("nocmp.go") |
||||
require.NoError(t, err, "unable to read nocmp.go") |
||||
|
||||
require.NoError(t, |
||||
os.WriteFile(filepath.Join(tempdir, "go.mod"), []byte(_exampleGoMod), 0o644), |
||||
"unable to write go.mod") |
||||
|
||||
require.NoError(t, |
||||
os.WriteFile(filepath.Join(tempdir, "nocmp.go"), nocmp, 0o644), |
||||
"unable to write nocmp.go") |
||||
|
||||
require.NoError(t, |
||||
os.WriteFile(filepath.Join(tempdir, "bad.go"), []byte(_badFile), 0o644), |
||||
"unable to write bad.go") |
||||
|
||||
var stderr bytes.Buffer |
||||
cmd := exec.Command("go", "build") |
||||
cmd.Dir = tempdir |
||||
// Create a minimal build environment with only HOME set so that "go
|
||||
// build" has somewhere to put the cache and other Go files in.
|
||||
cmd.Env = []string{"HOME=" + filepath.Join(tempdir, "home")} |
||||
cmd.Stderr = &stderr |
||||
require.Error(t, cmd.Run(), "bad.go must not compile") |
||||
|
||||
assert.Contains(t, stderr.String(), |
||||
"struct containing nocmp cannot be compared") |
||||
} |
||||
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2022 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.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package atomic |
||||
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
//
|
||||
// func TestPointer(t *testing.T) {
|
||||
// type foo struct{ v int }
|
||||
//
|
||||
// i := foo{42}
|
||||
// j := foo{0}
|
||||
// k := foo{1}
|
||||
//
|
||||
// tests := []struct {
|
||||
// desc string
|
||||
// newAtomic func() *Pointer[foo]
|
||||
// initial *foo
|
||||
// }{
|
||||
// {
|
||||
// desc: "New",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// return NewPointer(&i)
|
||||
// },
|
||||
// initial: &i,
|
||||
// },
|
||||
// {
|
||||
// desc: "New/nil",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// return NewPointer[foo](nil)
|
||||
// },
|
||||
// initial: nil,
|
||||
// },
|
||||
// {
|
||||
// desc: "zero value",
|
||||
// newAtomic: func() *Pointer[foo] {
|
||||
// var p Pointer[foo]
|
||||
// return &p
|
||||
// },
|
||||
// initial: nil,
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.desc, func(t *testing.T) {
|
||||
// t.Run("Load", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, tt.initial, atom.Load(), "Load should report nil.")
|
||||
// })
|
||||
//
|
||||
// t.Run("Swap", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, tt.initial, atom.Swap(&k), "Swap didn't return the old value.")
|
||||
// require.Equal(t, &k, atom.Load(), "Swap didn't set the correct value.")
|
||||
// })
|
||||
//
|
||||
// t.Run("CAS", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.True(t, atom.CompareAndSwap(tt.initial, &j), "CAS didn't report a swap.")
|
||||
// require.Equal(t, &j, atom.Load(), "CAS didn't set the correct value.")
|
||||
// })
|
||||
//
|
||||
// t.Run("Store", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// atom.Store(&i)
|
||||
// require.Equal(t, &i, atom.Load(), "Store didn't set the correct value.")
|
||||
// })
|
||||
// t.Run("String", func(t *testing.T) {
|
||||
// atom := tt.newAtomic()
|
||||
// require.Equal(t, fmt.Sprint(tt.initial), atom.String(), "String did not return the correct value.")
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
@ -0,0 +1,289 @@
@@ -0,0 +1,289 @@
|
||||
// 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" |
||||
"math" |
||||
"runtime" |
||||
"sync" |
||||
"sync/atomic" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
_parallelism = 4 |
||||
_iterations = 1000 |
||||
) |
||||
|
||||
var _stressTests = map[string]func() func(){ |
||||
"i32/std": stressStdInt32, |
||||
"i32": stressInt32, |
||||
"i64/std": stressStdInt64, |
||||
"i64": stressInt64, |
||||
"u32/std": stressStdUint32, |
||||
"u32": stressUint32, |
||||
"u64/std": stressStdUint64, |
||||
"u64": stressUint64, |
||||
"f64": stressFloat64, |
||||
"bool": stressBool, |
||||
"string": stressString, |
||||
"duration": stressDuration, |
||||
"error": stressError, |
||||
"time": stressTime, |
||||
} |
||||
|
||||
func TestStress(t *testing.T) { |
||||
for name, ff := range _stressTests { |
||||
t.Run(name, func(t *testing.T) { |
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(_parallelism)) |
||||
|
||||
start := make(chan struct{}) |
||||
var wg sync.WaitGroup |
||||
wg.Add(_parallelism) |
||||
f := ff() |
||||
for i := 0; i < _parallelism; i++ { |
||||
go func() { |
||||
defer wg.Done() |
||||
<-start |
||||
for j := 0; j < _iterations; j++ { |
||||
f() |
||||
} |
||||
}() |
||||
} |
||||
close(start) |
||||
wg.Wait() |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkStress(b *testing.B) { |
||||
for name, ff := range _stressTests { |
||||
b.Run(name, func(b *testing.B) { |
||||
f := ff() |
||||
|
||||
b.Run("serial", func(b *testing.B) { |
||||
for i := 0; i < b.N; i++ { |
||||
f() |
||||
} |
||||
}) |
||||
|
||||
b.Run("parallel", func(b *testing.B) { |
||||
b.RunParallel(func(pb *testing.PB) { |
||||
for pb.Next() { |
||||
f() |
||||
} |
||||
}) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func stressStdInt32() func() { |
||||
var atom int32 |
||||
return func() { |
||||
atomic.LoadInt32(&atom) |
||||
atomic.AddInt32(&atom, 1) |
||||
atomic.AddInt32(&atom, -2) |
||||
atomic.AddInt32(&atom, 1) |
||||
atomic.AddInt32(&atom, -1) |
||||
atomic.CompareAndSwapInt32(&atom, 1, 0) |
||||
atomic.SwapInt32(&atom, 5) |
||||
atomic.StoreInt32(&atom, 1) |
||||
} |
||||
} |
||||
|
||||
func stressInt32() func() { |
||||
var atom Int32 |
||||
return func() { |
||||
atom.Load() |
||||
atom.Add(1) |
||||
atom.Sub(2) |
||||
atom.Inc() |
||||
atom.Dec() |
||||
atom.CAS(1, 0) |
||||
atom.Swap(5) |
||||
atom.Store(1) |
||||
} |
||||
} |
||||
|
||||
func stressStdInt64() func() { |
||||
var atom int64 |
||||
return func() { |
||||
atomic.LoadInt64(&atom) |
||||
atomic.AddInt64(&atom, 1) |
||||
atomic.AddInt64(&atom, -2) |
||||
atomic.AddInt64(&atom, 1) |
||||
atomic.AddInt64(&atom, -1) |
||||
atomic.CompareAndSwapInt64(&atom, 1, 0) |
||||
atomic.SwapInt64(&atom, 5) |
||||
atomic.StoreInt64(&atom, 1) |
||||
} |
||||
} |
||||
|
||||
func stressInt64() func() { |
||||
var atom Int64 |
||||
return func() { |
||||
atom.Load() |
||||
atom.Add(1) |
||||
atom.Sub(2) |
||||
atom.Inc() |
||||
atom.Dec() |
||||
atom.CAS(1, 0) |
||||
atom.Swap(5) |
||||
atom.Store(1) |
||||
} |
||||
} |
||||
|
||||
func stressStdUint32() func() { |
||||
var atom uint32 |
||||
return func() { |
||||
atomic.LoadUint32(&atom) |
||||
atomic.AddUint32(&atom, 1) |
||||
// Adding `MaxUint32` is the same as subtracting 1
|
||||
atomic.AddUint32(&atom, math.MaxUint32-1) |
||||
atomic.AddUint32(&atom, 1) |
||||
atomic.AddUint32(&atom, math.MaxUint32) |
||||
atomic.CompareAndSwapUint32(&atom, 1, 0) |
||||
atomic.SwapUint32(&atom, 5) |
||||
atomic.StoreUint32(&atom, 1) |
||||
} |
||||
} |
||||
|
||||
func stressUint32() func() { |
||||
var atom Uint32 |
||||
return func() { |
||||
atom.Load() |
||||
atom.Add(1) |
||||
atom.Sub(2) |
||||
atom.Inc() |
||||
atom.Dec() |
||||
atom.CAS(1, 0) |
||||
atom.Swap(5) |
||||
atom.Store(1) |
||||
} |
||||
} |
||||
|
||||
func stressStdUint64() func() { |
||||
var atom uint64 |
||||
return func() { |
||||
atomic.LoadUint64(&atom) |
||||
atomic.AddUint64(&atom, 1) |
||||
// Adding `MaxUint64` is the same as subtracting 1
|
||||
atomic.AddUint64(&atom, math.MaxUint64-1) |
||||
atomic.AddUint64(&atom, 1) |
||||
atomic.AddUint64(&atom, math.MaxUint64) |
||||
atomic.CompareAndSwapUint64(&atom, 1, 0) |
||||
atomic.SwapUint64(&atom, 5) |
||||
atomic.StoreUint64(&atom, 1) |
||||
} |
||||
} |
||||
|
||||
func stressUint64() func() { |
||||
var atom Uint64 |
||||
return func() { |
||||
atom.Load() |
||||
atom.Add(1) |
||||
atom.Sub(2) |
||||
atom.Inc() |
||||
atom.Dec() |
||||
atom.CAS(1, 0) |
||||
atom.Swap(5) |
||||
atom.Store(1) |
||||
} |
||||
} |
||||
|
||||
func stressFloat64() func() { |
||||
var atom Float64 |
||||
return func() { |
||||
atom.Load() |
||||
atom.CAS(1.0, 0.1) |
||||
atom.Add(1.1) |
||||
atom.Sub(0.2) |
||||
atom.Store(1.0) |
||||
} |
||||
} |
||||
|
||||
func stressBool() func() { |
||||
var atom Bool |
||||
return func() { |
||||
atom.Load() |
||||
atom.Store(false) |
||||
atom.Swap(true) |
||||
atom.CAS(true, false) |
||||
atom.CAS(true, false) |
||||
atom.Load() |
||||
atom.Toggle() |
||||
atom.Toggle() |
||||
} |
||||
} |
||||
|
||||
func stressString() func() { |
||||
var atom String |
||||
return func() { |
||||
atom.Load() |
||||
atom.Store("abc") |
||||
atom.Load() |
||||
atom.Store("def") |
||||
atom.Load() |
||||
atom.Store("") |
||||
} |
||||
} |
||||
|
||||
func stressDuration() func() { |
||||
var atom = NewDuration(0) |
||||
return func() { |
||||
atom.Load() |
||||
atom.Add(1) |
||||
atom.Sub(2) |
||||
atom.CAS(1, 0) |
||||
atom.Swap(5) |
||||
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) |
||||
} |
||||
} |
||||
|
||||
func stressTime() func() { |
||||
var atom = NewTime(time.Date(2021, 6, 17, 9, 0, 0, 0, time.UTC)) |
||||
var dayAgo = time.Date(2021, 6, 16, 9, 0, 0, 0, time.UTC) |
||||
var weekAgo = time.Date(2021, 6, 10, 9, 0, 0, 0, time.UTC) |
||||
return func() { |
||||
atom.Load() |
||||
atom.Store(dayAgo) |
||||
atom.Load() |
||||
atom.Store(weekAgo) |
||||
atom.Store(time.Time{}) |
||||
} |
||||
} |
||||
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 |
||||
|
||||
// String is an atomic type-safe wrapper for string values.
|
||||
type String struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value |
||||
} |
||||
|
||||
var _zeroString string |
||||
|
||||
// NewString creates a new String.
|
||||
func NewString(val string) *String { |
||||
x := &String{} |
||||
if val != _zeroString { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped string.
|
||||
func (x *String) Load() string { |
||||
return unpackString(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed string.
|
||||
func (x *String) Store(val string) { |
||||
x.v.Store(packString(val)) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap for string values.
|
||||
func (x *String) CompareAndSwap(old, new string) (swapped bool) { |
||||
if x.v.CompareAndSwap(packString(old), packString(new)) { |
||||
return true |
||||
} |
||||
|
||||
if old == _zeroString { |
||||
// If the old value is the empty value, then it's possible the
|
||||
// underlying Value hasn't been set and is nil, so retry with nil.
|
||||
return x.v.CompareAndSwap(nil, packString(new)) |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
// Swap atomically stores the given string and returns the old
|
||||
// value.
|
||||
func (x *String) Swap(val string) (old string) { |
||||
return unpackString(x.v.Swap(packString(val))) |
||||
} |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2020-2023 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 |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped Value -pack packString -unpack unpackString -compareandswap -swap -file=string.go
|
||||
|
||||
func packString(s string) interface{} { |
||||
return s |
||||
} |
||||
|
||||
func unpackString(v interface{}) string { |
||||
if s, ok := v.(string); ok { |
||||
return s |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// String returns the wrapped value.
|
||||
func (s *String) String() string { |
||||
return s.Load() |
||||
} |
||||
|
||||
// MarshalText encodes the wrapped string into a textual form.
|
||||
//
|
||||
// This makes it encodable as JSON, YAML, XML, and more.
|
||||
func (s *String) MarshalText() ([]byte, error) { |
||||
return []byte(s.Load()), nil |
||||
} |
||||
|
||||
// UnmarshalText decodes text and replaces the wrapped string with it.
|
||||
//
|
||||
// This makes it decodable from JSON, YAML, XML, and more.
|
||||
func (s *String) UnmarshalText(b []byte) error { |
||||
s.Store(string(b)) |
||||
return nil |
||||
} |
||||
@ -0,0 +1,170 @@
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2016-2020 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 ( |
||||
"encoding/json" |
||||
"encoding/xml" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestStringNoInitialValue(t *testing.T) { |
||||
atom := &String{} |
||||
require.Equal(t, "", atom.Load(), "Initial value should be blank string") |
||||
} |
||||
|
||||
func TestString(t *testing.T) { |
||||
atom := NewString("") |
||||
require.Equal(t, "", atom.Load(), "Expected Load to return initialized value") |
||||
|
||||
atom.Store("abc") |
||||
require.Equal(t, "abc", atom.Load(), "Unexpected value after Store") |
||||
|
||||
atom = NewString("bcd") |
||||
require.Equal(t, "bcd", atom.Load(), "Expected Load to return initialized value") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte(`"bcd"`), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"abc"`), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, "abc", atom.Load(), "json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("42"), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
atom = NewString("foo") |
||||
|
||||
t.Run("XML/Marshal", func(t *testing.T) { |
||||
bytes, err := xml.Marshal(atom) |
||||
require.NoError(t, err, "xml.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("<String>foo</String>"), bytes, |
||||
"xml.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("XML/Unmarshal", func(t *testing.T) { |
||||
err := xml.Unmarshal([]byte("<String>bar</String>"), &atom) |
||||
require.NoError(t, err, "xml.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, "bar", atom.Load(), "xml.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
atom := NewString("foo") |
||||
assert.Equal(t, "foo", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) { |
||||
atom := NewString("foo") |
||||
|
||||
swapped := atom.CompareAndSwap("bar", "bar") |
||||
require.False(t, swapped, "swapped isn't false") |
||||
require.Equal(t, atom.Load(), "foo", "Load returned wrong value") |
||||
|
||||
swapped = atom.CompareAndSwap("foo", "bar") |
||||
require.True(t, swapped, "swapped isn't true") |
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value") |
||||
}) |
||||
|
||||
t.Run("Swap", func(t *testing.T) { |
||||
atom := NewString("foo") |
||||
|
||||
old := atom.Swap("bar") |
||||
require.Equal(t, old, "foo", "Swap returned wrong value") |
||||
require.Equal(t, atom.Load(), "bar", "Load returned wrong value") |
||||
}) |
||||
} |
||||
|
||||
func TestString_InitializeDefault(t *testing.T) { |
||||
tests := []struct { |
||||
msg string |
||||
newStr func() *String |
||||
}{ |
||||
{ |
||||
msg: "Uninitialized", |
||||
newStr: func() *String { |
||||
var s String |
||||
return &s |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "NewString with default", |
||||
newStr: func() *String { |
||||
return NewString("") |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "String swapped with default", |
||||
newStr: func() *String { |
||||
s := NewString("initial") |
||||
s.Swap("") |
||||
return s |
||||
}, |
||||
}, |
||||
{ |
||||
msg: "String CAS'd with default", |
||||
newStr: func() *String { |
||||
s := NewString("initial") |
||||
s.CompareAndSwap("initial", "") |
||||
return s |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.msg, func(t *testing.T) { |
||||
t.Run("MarshalText", func(t *testing.T) { |
||||
str := tt.newStr() |
||||
text, err := str.MarshalText() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, "", string(text), "") |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
str := tt.newStr() |
||||
assert.Equal(t, "", str.String()) |
||||
}) |
||||
|
||||
t.Run("CompareAndSwap", func(t *testing.T) { |
||||
str := tt.newStr() |
||||
require.True(t, str.CompareAndSwap("", "new")) |
||||
assert.Equal(t, "new", str.Load()) |
||||
}) |
||||
|
||||
t.Run("Swap", func(t *testing.T) { |
||||
str := tt.newStr() |
||||
assert.Equal(t, "", str.Swap("new")) |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
// @generated Code generated by gen-atomicwrapper.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"time" |
||||
) |
||||
|
||||
// Time is an atomic type-safe wrapper for time.Time values.
|
||||
type Time struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v Value |
||||
} |
||||
|
||||
var _zeroTime time.Time |
||||
|
||||
// NewTime creates a new Time.
|
||||
func NewTime(val time.Time) *Time { |
||||
x := &Time{} |
||||
if val != _zeroTime { |
||||
x.Store(val) |
||||
} |
||||
return x |
||||
} |
||||
|
||||
// Load atomically loads the wrapped time.Time.
|
||||
func (x *Time) Load() time.Time { |
||||
return unpackTime(x.v.Load()) |
||||
} |
||||
|
||||
// Store atomically stores the passed time.Time.
|
||||
func (x *Time) Store(val time.Time) { |
||||
x.v.Store(packTime(val)) |
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2021 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 "time" |
||||
|
||||
//go:generate bin/gen-atomicwrapper -name=Time -type=time.Time -wrapped=Value -pack=packTime -unpack=unpackTime -imports time -file=time.go
|
||||
|
||||
func packTime(t time.Time) interface{} { |
||||
return t |
||||
} |
||||
|
||||
func unpackTime(v interface{}) time.Time { |
||||
if t, ok := v.(time.Time); ok { |
||||
return t |
||||
} |
||||
return time.Time{} |
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2021 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 ( |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestTime(t *testing.T) { |
||||
start := time.Date(2021, 6, 17, 9, 10, 0, 0, time.UTC) |
||||
atom := NewTime(start) |
||||
|
||||
require.Equal(t, start, atom.Load(), "Load didn't work") |
||||
require.Equal(t, time.Time{}, NewTime(time.Time{}).Load(), "Default time value is wrong") |
||||
} |
||||
|
||||
func TestTimeLocation(t *testing.T) { |
||||
// Check TZ data hasn't been lost from load/store.
|
||||
ny, err := time.LoadLocation("America/New_York") |
||||
require.NoError(t, err, "Failed to load location") |
||||
nyTime := NewTime(time.Date(2021, 1, 1, 0, 0, 0, 0, ny)) |
||||
|
||||
var atom Time |
||||
atom.Store(nyTime.Load()) |
||||
|
||||
assert.Equal(t, ny, atom.Load().Location(), "Location information is wrong") |
||||
} |
||||
|
||||
func TestLargeTime(t *testing.T) { |
||||
// Check "large/small" time that are beyond int64 ns
|
||||
// representation (< year 1678 or > year 2262) can be
|
||||
// correctly load/store'd.
|
||||
t.Parallel() |
||||
|
||||
t.Run("future", func(t *testing.T) { |
||||
future := time.Date(2262, 12, 31, 0, 0, 0, 0, time.UTC) |
||||
atom := NewTime(future) |
||||
dayAfterFuture := atom.Load().AddDate(0, 1, 0) |
||||
|
||||
atom.Store(dayAfterFuture) |
||||
assert.Equal(t, 2263, atom.Load().Year()) |
||||
}) |
||||
|
||||
t.Run("past", func(t *testing.T) { |
||||
past := time.Date(1678, 1, 1, 0, 0, 0, 0, time.UTC) |
||||
atom := NewTime(past) |
||||
dayBeforePast := atom.Load().AddDate(0, -1, 0) |
||||
|
||||
atom.Store(dayBeforePast) |
||||
assert.Equal(t, 1677, atom.Load().Year()) |
||||
}) |
||||
} |
||||
|
||||
func TestMonotonic(t *testing.T) { |
||||
before := NewTime(time.Now()) |
||||
time.Sleep(15 * time.Millisecond) |
||||
after := NewTime(time.Now()) |
||||
|
||||
// try loading/storing before and test monotonic clock value hasn't been lost
|
||||
bt := before.Load() |
||||
before.Store(bt) |
||||
d := after.Load().Sub(before.Load()) |
||||
assert.True(t, 15 <= d.Milliseconds()) |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2019 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.
|
||||
|
||||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package tools |
||||
|
||||
import ( |
||||
// Tools used during development.
|
||||
_ "golang.org/x/lint/golint" |
||||
_ "honnef.co/go/tools/cmd/staticcheck" |
||||
) |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Uint32 is an atomic wrapper around uint32.
|
||||
type Uint32 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uint32 |
||||
} |
||||
|
||||
// NewUint32 creates a new Uint32.
|
||||
func NewUint32(val uint32) *Uint32 { |
||||
return &Uint32{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint32) Load() uint32 { |
||||
return atomic.LoadUint32(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Add(delta uint32) uint32 { |
||||
return atomic.AddUint32(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Sub(delta uint32) uint32 { |
||||
return atomic.AddUint32(&i.v, ^(delta - 1)) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Inc() uint32 { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Dec() uint32 { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint32) CAS(old, new uint32) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint32) CompareAndSwap(old, new uint32) (swapped bool) { |
||||
return atomic.CompareAndSwapUint32(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint32) Store(val uint32) { |
||||
atomic.StoreUint32(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||
func (i *Uint32) Swap(val uint32) (old uint32) { |
||||
return atomic.SwapUint32(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped uint32 into JSON.
|
||||
func (i *Uint32) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uint32.
|
||||
func (i *Uint32) UnmarshalJSON(b []byte) error { |
||||
var v uint32 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uint32) String() string { |
||||
v := i.Load() |
||||
return strconv.FormatUint(uint64(v), 10) |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestUint32(t *testing.T) { |
||||
atom := NewUint32(42) |
||||
|
||||
require.Equal(t, uint32(42), atom.Load(), "Load didn't work.") |
||||
require.Equal(t, uint32(46), atom.Add(4), "Add didn't work.") |
||||
require.Equal(t, uint32(44), atom.Sub(2), "Sub didn't work.") |
||||
require.Equal(t, uint32(45), atom.Inc(), "Inc didn't work.") |
||||
require.Equal(t, uint32(44), atom.Dec(), "Dec didn't work.") |
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") |
||||
require.Equal(t, uint32(0), atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, uint32(0), atom.Swap(1), "Swap didn't return the old value.") |
||||
require.Equal(t, uint32(1), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(42) |
||||
require.Equal(t, uint32(42), atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, uint32(40), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"40"`), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
atom := NewUint32(math.MaxUint32) |
||||
assert.Equal(t, "4294967295", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Uint64 is an atomic wrapper around uint64.
|
||||
type Uint64 struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uint64 |
||||
} |
||||
|
||||
// NewUint64 creates a new Uint64.
|
||||
func NewUint64(val uint64) *Uint64 { |
||||
return &Uint64{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint64) Load() uint64 { |
||||
return atomic.LoadUint64(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Add(delta uint64) uint64 { |
||||
return atomic.AddUint64(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Sub(delta uint64) uint64 { |
||||
return atomic.AddUint64(&i.v, ^(delta - 1)) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Inc() uint64 { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Dec() uint64 { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uint64) CAS(old, new uint64) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uint64) CompareAndSwap(old, new uint64) (swapped bool) { |
||||
return atomic.CompareAndSwapUint64(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint64) Store(val uint64) { |
||||
atomic.StoreUint64(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped uint64 and returns the old value.
|
||||
func (i *Uint64) Swap(val uint64) (old uint64) { |
||||
return atomic.SwapUint64(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped uint64 into JSON.
|
||||
func (i *Uint64) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uint64.
|
||||
func (i *Uint64) UnmarshalJSON(b []byte) error { |
||||
var v uint64 |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uint64) String() string { |
||||
v := i.Load() |
||||
return strconv.FormatUint(uint64(v), 10) |
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"encoding/json" |
||||
"math" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestUint64(t *testing.T) { |
||||
atom := NewUint64(42) |
||||
|
||||
require.Equal(t, uint64(42), atom.Load(), "Load didn't work.") |
||||
require.Equal(t, uint64(46), atom.Add(4), "Add didn't work.") |
||||
require.Equal(t, uint64(44), atom.Sub(2), "Sub didn't work.") |
||||
require.Equal(t, uint64(45), atom.Inc(), "Inc didn't work.") |
||||
require.Equal(t, uint64(44), atom.Dec(), "Dec didn't work.") |
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") |
||||
require.Equal(t, uint64(0), atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, uint64(0), atom.Swap(1), "Swap didn't return the old value.") |
||||
require.Equal(t, uint64(1), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(42) |
||||
require.Equal(t, uint64(42), atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, uint64(40), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"40"`), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
atom := NewUint64(math.MaxUint64) |
||||
assert.Equal(t, "18446744073709551615", atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
// @generated Code generated by gen-atomicint.
|
||||
|
||||
// Copyright (c) 2020-2023 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 ( |
||||
"encoding/json" |
||||
"strconv" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
// Uintptr is an atomic wrapper around uintptr.
|
||||
type Uintptr struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v uintptr |
||||
} |
||||
|
||||
// NewUintptr creates a new Uintptr.
|
||||
func NewUintptr(val uintptr) *Uintptr { |
||||
return &Uintptr{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uintptr) Load() uintptr { |
||||
return atomic.LoadUintptr(&i.v) |
||||
} |
||||
|
||||
// Add atomically adds to the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Add(delta uintptr) uintptr { |
||||
return atomic.AddUintptr(&i.v, delta) |
||||
} |
||||
|
||||
// Sub atomically subtracts from the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Sub(delta uintptr) uintptr { |
||||
return atomic.AddUintptr(&i.v, ^(delta - 1)) |
||||
} |
||||
|
||||
// Inc atomically increments the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Inc() uintptr { |
||||
return i.Add(1) |
||||
} |
||||
|
||||
// Dec atomically decrements the wrapped uintptr and returns the new value.
|
||||
func (i *Uintptr) Dec() uintptr { |
||||
return i.Sub(1) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap.
|
||||
func (i *Uintptr) CAS(old, new uintptr) (swapped bool) { |
||||
return i.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (i *Uintptr) CompareAndSwap(old, new uintptr) (swapped bool) { |
||||
return atomic.CompareAndSwapUintptr(&i.v, old, new) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uintptr) Store(val uintptr) { |
||||
atomic.StoreUintptr(&i.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped uintptr and returns the old value.
|
||||
func (i *Uintptr) Swap(val uintptr) (old uintptr) { |
||||
return atomic.SwapUintptr(&i.v, val) |
||||
} |
||||
|
||||
// MarshalJSON encodes the wrapped uintptr into JSON.
|
||||
func (i *Uintptr) MarshalJSON() ([]byte, error) { |
||||
return json.Marshal(i.Load()) |
||||
} |
||||
|
||||
// UnmarshalJSON decodes JSON into the wrapped uintptr.
|
||||
func (i *Uintptr) UnmarshalJSON(b []byte) error { |
||||
var v uintptr |
||||
if err := json.Unmarshal(b, &v); err != nil { |
||||
return err |
||||
} |
||||
i.Store(v) |
||||
return nil |
||||
} |
||||
|
||||
// String encodes the wrapped value as a string.
|
||||
func (i *Uintptr) String() string { |
||||
v := i.Load() |
||||
return strconv.FormatUint(uint64(v), 10) |
||||
} |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2021 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 ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestUintptr(t *testing.T) { |
||||
atom := NewUintptr(42) |
||||
|
||||
require.Equal(t, uintptr(42), atom.Load(), "Load didn't work.") |
||||
require.Equal(t, uintptr(46), atom.Add(4), "Add didn't work.") |
||||
require.Equal(t, uintptr(44), atom.Sub(2), "Sub didn't work.") |
||||
require.Equal(t, uintptr(45), atom.Inc(), "Inc didn't work.") |
||||
require.Equal(t, uintptr(44), atom.Dec(), "Dec didn't work.") |
||||
|
||||
require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.") |
||||
require.Equal(t, uintptr(0), atom.Load(), "CAS didn't set the correct value.") |
||||
|
||||
require.Equal(t, uintptr(0), atom.Swap(1), "Swap didn't return the old value.") |
||||
require.Equal(t, uintptr(1), atom.Load(), "Swap didn't set the correct value.") |
||||
|
||||
atom.Store(42) |
||||
require.Equal(t, uintptr(42), atom.Load(), "Store didn't set the correct value.") |
||||
|
||||
t.Run("JSON/Marshal", func(t *testing.T) { |
||||
bytes, err := json.Marshal(atom) |
||||
require.NoError(t, err, "json.Marshal errored unexpectedly.") |
||||
require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte("40"), &atom) |
||||
require.NoError(t, err, "json.Unmarshal errored unexpectedly.") |
||||
require.Equal(t, uintptr(40), atom.Load(), |
||||
"json.Unmarshal didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("JSON/Unmarshal/Error", func(t *testing.T) { |
||||
err := json.Unmarshal([]byte(`"40"`), &atom) |
||||
require.Error(t, err, "json.Unmarshal didn't error as expected.") |
||||
assertErrorJSONUnmarshalType(t, err, |
||||
"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err) |
||||
}) |
||||
|
||||
t.Run("String", func(t *testing.T) { |
||||
// Use an integer with the signed bit set. If we're converting
|
||||
// incorrectly, we'll get a negative value here.
|
||||
// Use an int variable, as constants cause compile-time overflows.
|
||||
negative := -1 |
||||
atom := NewUintptr(uintptr(negative)) |
||||
want := fmt.Sprint(uintptr(negative)) |
||||
assert.Equal(t, want, atom.String(), |
||||
"String() returned an unexpected value.") |
||||
}) |
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2021-2022 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 ( |
||||
"sync/atomic" |
||||
"unsafe" |
||||
) |
||||
|
||||
// UnsafePointer is an atomic wrapper around unsafe.Pointer.
|
||||
type UnsafePointer struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
v unsafe.Pointer |
||||
} |
||||
|
||||
// NewUnsafePointer creates a new UnsafePointer.
|
||||
func NewUnsafePointer(val unsafe.Pointer) *UnsafePointer { |
||||
return &UnsafePointer{v: val} |
||||
} |
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (p *UnsafePointer) Load() unsafe.Pointer { |
||||
return atomic.LoadPointer(&p.v) |
||||
} |
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (p *UnsafePointer) Store(val unsafe.Pointer) { |
||||
atomic.StorePointer(&p.v, val) |
||||
} |
||||
|
||||
// Swap atomically swaps the wrapped unsafe.Pointer and returns the old value.
|
||||
func (p *UnsafePointer) Swap(val unsafe.Pointer) (old unsafe.Pointer) { |
||||
return atomic.SwapPointer(&p.v, val) |
||||
} |
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
//
|
||||
// Deprecated: Use CompareAndSwap
|
||||
func (p *UnsafePointer) CAS(old, new unsafe.Pointer) (swapped bool) { |
||||
return p.CompareAndSwap(old, new) |
||||
} |
||||
|
||||
// CompareAndSwap is an atomic compare-and-swap.
|
||||
func (p *UnsafePointer) CompareAndSwap(old, new unsafe.Pointer) (swapped bool) { |
||||
return atomic.CompareAndSwapPointer(&p.v, old, new) |
||||
} |
||||
@ -0,0 +1,83 @@
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2021 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 ( |
||||
"testing" |
||||
"unsafe" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestUnsafePointer(t *testing.T) { |
||||
i := int64(42) |
||||
j := int64(0) |
||||
k := int64(1) |
||||
|
||||
tests := []struct { |
||||
desc string |
||||
newAtomic func() *UnsafePointer |
||||
initial unsafe.Pointer |
||||
}{ |
||||
{ |
||||
desc: "non-empty", |
||||
newAtomic: func() *UnsafePointer { |
||||
return NewUnsafePointer(unsafe.Pointer(&i)) |
||||
}, |
||||
initial: unsafe.Pointer(&i), |
||||
}, |
||||
{ |
||||
desc: "nil", |
||||
newAtomic: func() *UnsafePointer { |
||||
var p UnsafePointer |
||||
return &p |
||||
}, |
||||
initial: unsafe.Pointer(nil), |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
t.Run("Load", func(t *testing.T) { |
||||
atom := tt.newAtomic() |
||||
require.Equal(t, tt.initial, atom.Load(), "Load should report nil.") |
||||
}) |
||||
|
||||
t.Run("Swap", func(t *testing.T) { |
||||
atom := tt.newAtomic() |
||||
require.Equal(t, tt.initial, atom.Swap(unsafe.Pointer(&k)), "Swap didn't return the old value.") |
||||
require.Equal(t, unsafe.Pointer(&k), atom.Load(), "Swap didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("CAS", func(t *testing.T) { |
||||
atom := tt.newAtomic() |
||||
require.True(t, atom.CAS(tt.initial, unsafe.Pointer(&j)), "CAS didn't report a swap.") |
||||
require.Equal(t, unsafe.Pointer(&j), atom.Load(), "CAS didn't set the correct value.") |
||||
}) |
||||
|
||||
t.Run("Store", func(t *testing.T) { |
||||
atom := tt.newAtomic() |
||||
atom.Store(unsafe.Pointer(&i)) |
||||
require.Equal(t, unsafe.Pointer(&i), atom.Load(), "Store didn't set the correct value.") |
||||
}) |
||||
}) |
||||
} |
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2020 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 "sync/atomic" |
||||
|
||||
// Value shadows the type of the same name from sync/atomic
|
||||
// https://godoc.org/sync/atomic#Value
|
||||
type Value struct { |
||||
_ nocmp // disallow non-atomic comparison
|
||||
|
||||
atomic.Value |
||||
} |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 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 ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestValue(t *testing.T) { |
||||
var v Value |
||||
assert.Nil(t, v.Load(), "initial Value is not nil") |
||||
|
||||
v.Store(42) |
||||
assert.Equal(t, 42, v.Load()) |
||||
|
||||
v.Store(84) |
||||
assert.Equal(t, 84, v.Load()) |
||||
|
||||
assert.Panics(t, func() { v.Store("foo") }) |
||||
} |
||||
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
# interrupt |
||||
Handle shutdowns cleanly and enable hot reload |
||||
@ -0,0 +1,153 @@
@@ -0,0 +1,153 @@
|
||||
// Package interrupt is a library for providing handling for Ctrl-C/Interrupt
|
||||
// handling and triggering callbacks for such things as closing files, flushing
|
||||
// buffers, and other elements of graceful shutdowns.
|
||||
package interrupt |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"os/signal" |
||||
"runtime" |
||||
|
||||
"lol.mleku.dev/log" |
||||
"utils.orly/atomic" |
||||
"utils.orly/qu" |
||||
) |
||||
|
||||
// HandlerWithSource is an interrupt handling closure and the source location
|
||||
// that it was sent from.
|
||||
type HandlerWithSource struct { |
||||
Source string |
||||
Fn func() |
||||
} |
||||
|
||||
var ( |
||||
// RestartRequested is set true after restart is requested.
|
||||
RestartRequested bool // = true
|
||||
requested atomic.Bool |
||||
|
||||
// ch is used to receive SIGINT (Ctrl+C) signals.
|
||||
ch chan os.Signal |
||||
|
||||
// signals is the list of signals that cause the interrupt
|
||||
signals = []os.Signal{os.Interrupt} |
||||
|
||||
// ShutdownRequestChan is a channel that can receive shutdown requests
|
||||
ShutdownRequestChan = qu.T() |
||||
|
||||
// addHandlerChan is used to add an interrupt handler to the list of
|
||||
// handlers to be invoked on SIGINT (Ctrl+C) signals.
|
||||
addHandlerChan = make(chan HandlerWithSource) |
||||
|
||||
// HandlersDone is closed after all interrupt handlers run the first time an
|
||||
// interrupt is signaled.
|
||||
HandlersDone = make(qu.C) |
||||
|
||||
interruptCallbacks []func() |
||||
interruptCallbackSources []string |
||||
) |
||||
|
||||
// Listener listens for interrupt signals, registers interrupt callbacks, and
|
||||
// responds to custom shutdown signals as required
|
||||
func Listener() { |
||||
invokeCallbacks := func() { |
||||
// run handlers in LIFO order.
|
||||
for i := range interruptCallbacks { |
||||
idx := len(interruptCallbacks) - 1 - i |
||||
log.T.F( |
||||
"running callback %d from %s", idx, |
||||
interruptCallbackSources[idx], |
||||
) |
||||
interruptCallbacks[idx]() |
||||
} |
||||
log.D.Ln("interrupt handlers finished") |
||||
HandlersDone.Q() |
||||
if RestartRequested { |
||||
Restart() |
||||
} else { |
||||
os.Exit(0) |
||||
} |
||||
} |
||||
out: |
||||
for { |
||||
select { |
||||
case _ = <-ch: |
||||
fmt.Fprintf(os.Stderr, "\r") |
||||
requested.Store(true) |
||||
invokeCallbacks() |
||||
break out |
||||
|
||||
case <-ShutdownRequestChan.Wait(): |
||||
log.W.Ln("received shutdown request - shutting down...") |
||||
requested.Store(true) |
||||
invokeCallbacks() |
||||
break out |
||||
|
||||
case handler := <-addHandlerChan: |
||||
interruptCallbacks = append(interruptCallbacks, handler.Fn) |
||||
interruptCallbackSources = append( |
||||
interruptCallbackSources, |
||||
handler.Source, |
||||
) |
||||
|
||||
case <-HandlersDone.Wait(): |
||||
break out |
||||
} |
||||
} |
||||
} |
||||
|
||||
// AddHandler adds a handler to call when a SIGINT (Ctrl+C) is received.
|
||||
func AddHandler(handler func()) { |
||||
// Create the channel and start the main interrupt handler which invokes all
|
||||
// other callbacks and exits if not already done.
|
||||
_, loc, line, _ := runtime.Caller(1) |
||||
msg := fmt.Sprintf("%s:%d", loc, line) |
||||
if ch == nil { |
||||
ch = make(chan os.Signal) |
||||
signal.Notify(ch, signals...) |
||||
go Listener() |
||||
} |
||||
addHandlerChan <- HandlerWithSource{ |
||||
msg, handler, |
||||
} |
||||
} |
||||
|
||||
// Request programmatically requests a shutdown
|
||||
func Request() { |
||||
_, f, l, _ := runtime.Caller(1) |
||||
log.D.Ln("interrupt requested", f, l, requested.Load()) |
||||
if requested.Load() { |
||||
log.D.Ln("requested again") |
||||
return |
||||
} |
||||
requested.Store(true) |
||||
ShutdownRequestChan.Q() |
||||
var ok bool |
||||
select { |
||||
case _, ok = <-ShutdownRequestChan: |
||||
default: |
||||
} |
||||
if ok { |
||||
close(ShutdownRequestChan) |
||||
} |
||||
} |
||||
|
||||
// GoroutineDump returns a string with the current goroutine dump in order to
|
||||
// show what's going on in case of timeout.
|
||||
func GoroutineDump() string { |
||||
buf := make([]byte, 1<<18) |
||||
n := runtime.Stack(buf, true) |
||||
return string(buf[:n]) |
||||
} |
||||
|
||||
// RequestRestart sets the reset flag and requests a restart
|
||||
func RequestRestart() { |
||||
RestartRequested = true |
||||
log.D.Ln("requesting restart") |
||||
Request() |
||||
} |
||||
|
||||
// Requested returns true if an interrupt has been requested
|
||||
func Requested() bool { |
||||
return requested.Load() |
||||
} |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
//go:build linux
|
||||
|
||||
package interrupt |
||||
|
||||
import ( |
||||
"lol.mleku.dev/log" |
||||
"os" |
||||
"syscall" |
||||
|
||||
"github.com/kardianos/osext" |
||||
) |
||||
|
||||
// Restart uses syscall.Exec to restart the process. macOS and Windows are not
|
||||
// implemented, currently.
|
||||
func Restart() { |
||||
log.D.Ln("restarting") |
||||
file, e := osext.Executable() |
||||
if e != nil { |
||||
log.E.Ln(e) |
||||
return |
||||
} |
||||
e = syscall.Exec(file, os.Args, os.Environ()) |
||||
if e != nil { |
||||
log.F.Ln(e) |
||||
} |
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
package interrupt |
||||
|
||||
func Restart() { |
||||
// TODO: test this thing actually works!
|
||||
// log.D.Ln("doing windows restart")
|
||||
// // procAttr := new(os.ProcAttr)
|
||||
// // procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
// // os.StartProcess(os.Args[0], os.Args[1:], procAttr)
|
||||
// var s []string
|
||||
// // s = []string{"cmd.exe", "/C", "start"}
|
||||
// s = append(s, os.Args[0])
|
||||
// // s = append(s, "--delaystart")
|
||||
// s = append(s, os.Args[1:]...)
|
||||
// cmd := exec.Command(s[0], s[1:]...)
|
||||
// log.D.Ln("windows restart done")
|
||||
// if err := cmd.Start(); log.Fail(err) {
|
||||
// }
|
||||
// // select{}
|
||||
// os.Exit(0)
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
package interrupt |
||||
|
||||
func Restart() { |
||||
// TODO: test this thing actually works!
|
||||
// log.D.Ln("doing windows restart")
|
||||
// // procAttr := new(os.ProcAttr)
|
||||
// // procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
|
||||
// // os.StartProcess(os.Args[0], os.Args[1:], procAttr)
|
||||
// var s []string
|
||||
// // s = []string{"cmd.exe", "/C", "start"}
|
||||
// s = append(s, os.Args[0])
|
||||
// // s = append(s, "--delaystart")
|
||||
// s = append(s, os.Args[1:]...)
|
||||
// cmd := exec.Command(s[0], s[1:]...)
|
||||
// log.D.Ln("windows restart done")
|
||||
// if err := cmd.Start(); log.Fail(err) {
|
||||
// }
|
||||
// // select{}
|
||||
// os.Exit(0)
|
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package interrupt |
||||
|
||||
import ( |
||||
"os" |
||||
"syscall" |
||||
) |
||||
|
||||
func init() { |
||||
signals = []os.Signal{os.Interrupt, syscall.SIGTERM} |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue