implemented event and req
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
"go-simpler.org/env"
|
||||
lol "lol.mleku.dev"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/version"
|
||||
)
|
||||
|
||||
@@ -28,7 +27,8 @@ type C struct {
|
||||
DataDir string `env:"ORLY_DATA_DIR" usage:"storage location for the event store" default:"~/.local/share/ORLY"`
|
||||
Listen string `env:"ORLY_LISTEN" default:"0.0.0.0" usage:"network listen address"`
|
||||
Port int `env:"ORLY_PORT" default:"3334" usage:"port to listen on"`
|
||||
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"debug level: fatal error warn info debug trace"`
|
||||
LogLevel string `env:"ORLY_LOG_LEVEL" default:"info" usage:"relay log level: fatal error warn info debug trace"`
|
||||
DBLogLevel string `env:"ORLY_DB_LOG_LEVEL" default:"info" usage:"database log level: fatal error warn info debug trace"`
|
||||
Pprof string `env:"ORLY_PPROF" usage:"enable pprof in modes: cpu,memory,allocation"`
|
||||
IPWhitelist []string `env:"ORLY_IP_WHITELIST" usage:"comma-separated list of IP addresses to allow access from, matches on prefixes to allow private subnets, eg 10.0.0 = 10.0.0.0/8"`
|
||||
}
|
||||
@@ -71,7 +71,6 @@ func New() (cfg *C, err error) {
|
||||
os.Exit(0)
|
||||
}
|
||||
lol.SetLogLevel(cfg.LogLevel)
|
||||
log.I.S(cfg.IPWhitelist)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
1
app/handle-auth.go
Normal file
1
app/handle-auth.go
Normal file
@@ -0,0 +1 @@
|
||||
package app
|
||||
1
app/handle-close.go
Normal file
1
app/handle-close.go
Normal file
@@ -0,0 +1 @@
|
||||
package app
|
||||
66
app/handle-event.go
Normal file
66
app/handle-event.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"encoders.orly/envelopes/authenvelope"
|
||||
"encoders.orly/envelopes/closeenvelope"
|
||||
"encoders.orly/envelopes/eventenvelope"
|
||||
"encoders.orly/envelopes/noticeenvelope"
|
||||
"encoders.orly/envelopes/reqenvelope"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
func (s *Server) HandleMessage(msg []byte, remote string) {
|
||||
func (l *Listener) HandleMessage(msg []byte, remote string) {
|
||||
log.D.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
@@ -20,36 +22,36 @@ func (s *Server) HandleMessage(msg []byte, remote string) {
|
||||
)
|
||||
},
|
||||
)
|
||||
var notice []byte
|
||||
var err error
|
||||
var t string
|
||||
var rem []byte
|
||||
if t, rem, err = envelopes.Identify(msg); chk.E(err) {
|
||||
notice = []byte(err.Error())
|
||||
if t, rem, err = envelopes.Identify(msg); !chk.E(err) {
|
||||
switch t {
|
||||
case eventenvelope.L:
|
||||
log.D.F("eventenvelope: %s", rem)
|
||||
err = l.HandleEvent(l.ctx, rem)
|
||||
case reqenvelope.L:
|
||||
log.D.F("reqenvelope: %s", rem)
|
||||
err = l.HandleReq(l.ctx, rem)
|
||||
case closeenvelope.L:
|
||||
log.D.F("closeenvelope: %s", rem)
|
||||
case authenvelope.L:
|
||||
log.D.F("authenvelope: %s", rem)
|
||||
default:
|
||||
err = errorf.E("unknown envelope type %s\n%s", t, rem)
|
||||
}
|
||||
}
|
||||
switch t {
|
||||
case eventenvelope.L:
|
||||
log.D.F("eventenvelope: %s", rem)
|
||||
case reqenvelope.L:
|
||||
log.D.F("reqenvelope: %s", rem)
|
||||
case closeenvelope.L:
|
||||
log.D.F("closeenvelope: %s", rem)
|
||||
case authenvelope.L:
|
||||
log.D.F("authenvelope: %s", rem)
|
||||
default:
|
||||
notice = []byte(fmt.Sprintf("unknown envelope type %s\n%s", t, rem))
|
||||
}
|
||||
if len(notice) > 0 {
|
||||
if err != nil {
|
||||
log.D.C(
|
||||
func() string {
|
||||
return fmt.Sprintf(
|
||||
"notice->%s %s", remote, notice,
|
||||
"notice->%s %s", remote, err,
|
||||
)
|
||||
},
|
||||
)
|
||||
// if err = noticeenvelope.NewFrom(notice).Write(a.Listener); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
if err = noticeenvelope.NewFrom(err.Error()).Write(l); chk.E(err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
120
app/handle-req.go
Normal file
120
app/handle-req.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/coder/websocket"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"protocol.orly/publish"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -28,7 +29,7 @@ const (
|
||||
|
||||
func (s *Server) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
|
||||
remote := GetRemoteFromReq(r)
|
||||
log.D.F("handling websocket connection from %s", remote)
|
||||
log.T.F("handling websocket connection from %s", remote)
|
||||
if len(s.Config.IPWhitelist) > 0 {
|
||||
for _, ip := range s.Config.IPWhitelist {
|
||||
log.T.F("checking IP whitelist: %s", ip)
|
||||
@@ -52,6 +53,13 @@ whitelist:
|
||||
return
|
||||
}
|
||||
defer conn.CloseNow()
|
||||
listener := &Listener{
|
||||
ctx: s.Ctx,
|
||||
Server: s,
|
||||
conn: conn,
|
||||
remote: remote,
|
||||
}
|
||||
listener.publishers = publish.New(NewPublisher())
|
||||
go s.Pinger(s.Ctx, conn, time.NewTicker(time.Second*10), cancel)
|
||||
for {
|
||||
select {
|
||||
@@ -85,7 +93,7 @@ whitelist:
|
||||
}
|
||||
continue
|
||||
}
|
||||
go s.HandleMessage(msg, remote)
|
||||
go listener.HandleMessage(msg, remote)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
database "database.orly"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
|
||||
func Run(
|
||||
ctx context.Context, cfg *config.C, db *database.D,
|
||||
) (quit chan struct{}) {
|
||||
// shutdown handler
|
||||
go func() {
|
||||
select {
|
||||
@@ -23,6 +26,7 @@ func Run(ctx context.Context, cfg *config.C) (quit chan struct{}) {
|
||||
l := &Server{
|
||||
Ctx: ctx,
|
||||
Config: cfg,
|
||||
D: db,
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
|
||||
log.I.F("starting listener on http://%s", addr)
|
||||
|
||||
118
app/ok.go
Normal file
118
app/ok.go
Normal file
@@ -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)
|
||||
},
|
||||
}
|
||||
222
app/publisher.go
Normal file
222
app/publisher.go
Normal file
@@ -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()
|
||||
}
|
||||
@@ -4,14 +4,19 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"database.orly"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app/config"
|
||||
"protocol.orly/publish"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
mux *http.ServeMux
|
||||
Config *config.C
|
||||
Ctx context.Context
|
||||
remote string
|
||||
*database.D
|
||||
publishers *publish.S
|
||||
}
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
18
go.mod
18
go.mod
@@ -3,6 +3,7 @@ module next.orly.dev
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
database.orly v0.0.0-00010101000000-000000000000
|
||||
encoders.orly v0.0.0-00010101000000-000000000000
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/coder/websocket v1.8.13
|
||||
@@ -16,22 +17,39 @@ require (
|
||||
|
||||
require (
|
||||
crypto.orly v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/badger/v4 v4.8.0 // indirect
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/flatbuffers v25.2.10+incompatible // indirect
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/templexxx/cpu v0.0.1 // indirect
|
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
interfaces.orly v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
crypto.orly => ./pkg/crypto
|
||||
database.orly => ./pkg/database
|
||||
encoders.orly => ./pkg/encoders
|
||||
interfaces.orly => ./pkg/interfaces
|
||||
next.orly.dev => ../../
|
||||
|
||||
37
go.sum
37
go.sum
@@ -1,5 +1,7 @@
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -8,13 +10,34 @@ github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3C
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs=
|
||||
github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
|
||||
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
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/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
|
||||
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
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=
|
||||
@@ -37,12 +60,26 @@ github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3W
|
||||
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=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
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/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
9
main.go
9
main.go
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
database "database.orly"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/app"
|
||||
@@ -21,7 +22,13 @@ func main() {
|
||||
log.I.F("starting %s %s", cfg.AppName, version.V)
|
||||
startProfiler(cfg.Pprof)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
quit := app.Run(ctx, cfg)
|
||||
var db *database.D
|
||||
if db, err = database.New(
|
||||
ctx, cancel, cfg.DataDir, cfg.DBLogLevel,
|
||||
); chk.E(err) {
|
||||
os.Exit(1)
|
||||
}
|
||||
quit := app.Run(ctx, cfg, db)
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt)
|
||||
for {
|
||||
|
||||
@@ -2,6 +2,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
@@ -43,7 +44,8 @@ func New(
|
||||
return
|
||||
}
|
||||
|
||||
// Also ensure the directory exists using apputil.EnsureDir for any potential subdirectories
|
||||
// Also ensure the directory exists using apputil.EnsureDir for any
|
||||
// potential subdirectories
|
||||
dummyFile := filepath.Join(dataDir, "dummy.sst")
|
||||
if err = apputil.EnsureDir(dummyFile); chk.E(err) {
|
||||
return
|
||||
@@ -87,8 +89,8 @@ func New(
|
||||
func (d *D) Path() string { return d.dataDir }
|
||||
|
||||
func (d *D) Wipe() (err error) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
err = errors.New("not implemented")
|
||||
return
|
||||
}
|
||||
|
||||
func (d *D) SetLogLevel(level string) {
|
||||
@@ -98,8 +100,8 @@ func (d *D) SetLogLevel(level string) {
|
||||
func (d *D) EventIdsBySerial(start uint64, count int) (
|
||||
evs []uint64, err error,
|
||||
) {
|
||||
// TODO implement me
|
||||
panic("implement me")
|
||||
err = errors.New("not implemented")
|
||||
return
|
||||
}
|
||||
|
||||
// Init initializes the database with the given path.
|
||||
@@ -118,6 +120,7 @@ func (d *D) Sync() (err error) {
|
||||
|
||||
// Close releases resources and closes the database.
|
||||
func (d *D) Close() (err error) {
|
||||
log.D.F("%s: closing database", d.dataDir)
|
||||
if d.seq != nil {
|
||||
if err = d.seq.Release(); chk.E(err) {
|
||||
return
|
||||
|
||||
@@ -76,20 +76,21 @@ func GetIndexesForEvent(ev *event.E, serial uint64) (
|
||||
}
|
||||
// Process tags for tag-related indexes
|
||||
if ev.Tags != nil && ev.Tags.Len() > 0 {
|
||||
for _, tag := range ev.Tags.ToSliceOfTags() {
|
||||
for _, t := range *ev.Tags {
|
||||
// only index tags with a value field and the key is a single character
|
||||
if tag.Len() >= 2 {
|
||||
if t.Len() >= 2 {
|
||||
// Get the key and value from the tag
|
||||
keyBytes := tag.Key()
|
||||
keyBytes := t.Key()
|
||||
// require single-letter key
|
||||
if len(keyBytes) != 1 {
|
||||
continue
|
||||
}
|
||||
// if the key is not a-zA-Z skip
|
||||
if (keyBytes[0] < 'a' || keyBytes[0] > 'z') && (keyBytes[0] < 'A' || keyBytes[0] > 'Z') {
|
||||
if (keyBytes[0] < 'a' || keyBytes[0] > 'z') &&
|
||||
(keyBytes[0] < 'A' || keyBytes[0] > 'Z') {
|
||||
continue
|
||||
}
|
||||
valueBytes := tag.Value()
|
||||
valueBytes := t.Value()
|
||||
// Create tag key and value
|
||||
key := new(Letter)
|
||||
key.Set(keyBytes[0])
|
||||
|
||||
@@ -82,7 +82,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
// If there is any Ids in the filter, none of the other fields matter. It
|
||||
// should be an error, but convention just ignores it.
|
||||
if f.Ids.Len() > 0 {
|
||||
for _, id := range f.Ids.ToSliceOfBytes() {
|
||||
for _, id := range f.Ids.T {
|
||||
if err = func() (err error) {
|
||||
var i *types2.IdHash
|
||||
if i, err = CreateIdHashFromData(id); chk.E(err) {
|
||||
@@ -123,7 +123,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
|
||||
if f.Tags != nil && f.Tags.Len() > 0 {
|
||||
// sort the tags so they are in iteration order (reverse)
|
||||
tmp := f.Tags.ToSliceOfTags()
|
||||
tmp := *f.Tags
|
||||
sort.Slice(
|
||||
tmp, func(i, j int) bool {
|
||||
return bytes.Compare(tmp[i].Key(), tmp[j].Key()) > 0
|
||||
@@ -134,17 +134,17 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
// TagKindPubkey tkp
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Authors != nil && f.Authors.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 {
|
||||
for _, k := range f.Kinds.ToUint16() {
|
||||
for _, author := range f.Authors.ToSliceOfBytes() {
|
||||
for _, tag := range f.Tags.ToSliceOfTags() {
|
||||
for _, author := range f.Authors.T {
|
||||
for _, t := range *f.Tags {
|
||||
// accept single-letter keys like "e" or filter-style keys like "#e"
|
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) {
|
||||
if t.Len() >= 2 && (len(t.Key()) == 1 || (len(t.Key()) == 2 && t.Key()[0] == '#')) {
|
||||
kind := new(types2.Uint16)
|
||||
kind.Set(k)
|
||||
var p *types2.PubHash
|
||||
if p, err = CreatePubHashFromData(author); chk.E(err) {
|
||||
return
|
||||
}
|
||||
keyBytes := tag.Key()
|
||||
keyBytes := t.Key()
|
||||
key := new(types2.Letter)
|
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' {
|
||||
@@ -152,7 +152,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
} else {
|
||||
key.Set(keyBytes[0])
|
||||
}
|
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] {
|
||||
for _, valueBytes := range t.T[1:] {
|
||||
valueHash := new(types2.Ident)
|
||||
valueHash.FromIdent(valueBytes)
|
||||
start, end := new(bytes.Buffer), new(bytes.Buffer)
|
||||
@@ -184,11 +184,11 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
// TagKind tkc
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 {
|
||||
for _, k := range f.Kinds.ToUint16() {
|
||||
for _, tag := range f.Tags.ToSliceOfTags() {
|
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) {
|
||||
for _, t := range *f.Tags {
|
||||
if t.Len() >= 2 && (len(t.Key()) == 1 || (len(t.Key()) == 2 && t.Key()[0] == '#')) {
|
||||
kind := new(types2.Uint16)
|
||||
kind.Set(k)
|
||||
keyBytes := tag.Key()
|
||||
keyBytes := t.Key()
|
||||
key := new(types2.Letter)
|
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' {
|
||||
@@ -196,7 +196,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
} else {
|
||||
key.Set(keyBytes[0])
|
||||
}
|
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] {
|
||||
for _, valueBytes := range t.T[1:] {
|
||||
valueHash := new(types2.Ident)
|
||||
valueHash.FromIdent(valueBytes)
|
||||
start, end := new(bytes.Buffer), new(bytes.Buffer)
|
||||
@@ -226,14 +226,14 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
|
||||
// TagPubkey tpc
|
||||
if f.Authors != nil && f.Authors.Len() > 0 && f.Tags != nil && f.Tags.Len() > 0 {
|
||||
for _, author := range f.Authors.ToSliceOfBytes() {
|
||||
for _, tag := range f.Tags.ToSliceOfTags() {
|
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) {
|
||||
for _, author := range f.Authors.T {
|
||||
for _, t := range *f.Tags {
|
||||
if t.Len() >= 2 && (len(t.Key()) == 1 || (len(t.Key()) == 2 && t.Key()[0] == '#')) {
|
||||
var p *types2.PubHash
|
||||
if p, err = CreatePubHashFromData(author); chk.E(err) {
|
||||
return
|
||||
}
|
||||
keyBytes := tag.Key()
|
||||
keyBytes := t.Key()
|
||||
key := new(types2.Letter)
|
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' {
|
||||
@@ -241,7 +241,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
} else {
|
||||
key.Set(keyBytes[0])
|
||||
}
|
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] {
|
||||
for _, valueBytes := range t.T[1:] {
|
||||
valueHash := new(types2.Ident)
|
||||
valueHash.FromIdent(valueBytes)
|
||||
start, end := new(bytes.Buffer), new(bytes.Buffer)
|
||||
@@ -269,9 +269,9 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
|
||||
// Tag tc-
|
||||
if f.Tags != nil && f.Tags.Len() > 0 && (f.Authors == nil || f.Authors.Len() == 0) && (f.Kinds == nil || f.Kinds.Len() == 0) {
|
||||
for _, tag := range f.Tags.ToSliceOfTags() {
|
||||
if tag.Len() >= 2 && (len(tag.Key()) == 1 || (len(tag.Key()) == 2 && tag.Key()[0] == '#')) {
|
||||
keyBytes := tag.Key()
|
||||
for _, t := range *f.Tags {
|
||||
if t.Len() >= 2 && (len(t.Key()) == 1 || (len(t.Key()) == 2 && t.Key()[0] == '#')) {
|
||||
keyBytes := t.Key()
|
||||
key := new(types2.Letter)
|
||||
// If the tag key starts with '#', use the second character as the key
|
||||
if len(keyBytes) == 2 && keyBytes[0] == '#' {
|
||||
@@ -279,7 +279,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
} else {
|
||||
key.Set(keyBytes[0])
|
||||
}
|
||||
for _, valueBytes := range tag.ToSliceOfBytes()[1:] {
|
||||
for _, valueBytes := range t.T[1:] {
|
||||
valueHash := new(types2.Ident)
|
||||
valueHash.FromIdent(valueBytes)
|
||||
start, end := new(bytes.Buffer), new(bytes.Buffer)
|
||||
@@ -303,7 +303,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
// KindPubkey kpc
|
||||
if f.Kinds != nil && f.Kinds.Len() > 0 && f.Authors != nil && f.Authors.Len() > 0 {
|
||||
for _, k := range f.Kinds.ToUint16() {
|
||||
for _, author := range f.Authors.ToSliceOfBytes() {
|
||||
for _, author := range f.Authors.T {
|
||||
kind := new(types2.Uint16)
|
||||
kind.Set(k)
|
||||
p := new(types2.PubHash)
|
||||
@@ -350,7 +350,7 @@ func GetIndexesFromFilter(f *filter.F) (idxs []Range, err error) {
|
||||
|
||||
// Pubkey pc-
|
||||
if f.Authors != nil && f.Authors.Len() > 0 {
|
||||
for _, author := range f.Authors.ToSliceOfBytes() {
|
||||
for _, author := range f.Authors.T {
|
||||
p := new(types2.PubHash)
|
||||
if err = p.FromPubkey(author); chk.E(err) {
|
||||
return
|
||||
|
||||
@@ -42,7 +42,7 @@ func (d *D) QueryEvents(c context.Context, f *filter.F) (
|
||||
var expDeletes types.Uint40s
|
||||
var expEvs event.S
|
||||
if f.Ids != nil && f.Ids.Len() > 0 {
|
||||
for _, idx := range f.Ids.ToSliceOfBytes() {
|
||||
for _, idx := range f.Ids.T {
|
||||
// we know there is only Ids in this, so run the ID query and fetch.
|
||||
var ser *types.Uint40
|
||||
if ser, err = d.GetSerialById(idx); chk.E(err) {
|
||||
|
||||
@@ -17,7 +17,7 @@ func (d *D) QueryForSerials(c context.Context, f *filter.F) (
|
||||
var founds []*types.Uint40
|
||||
var idPkTs []*store.IdPkTs
|
||||
if f.Ids != nil && f.Ids.Len() > 0 {
|
||||
for _, id := range f.Ids.ToSliceOfBytes() {
|
||||
for _, id := range f.Ids.T {
|
||||
var ser *types.Uint40
|
||||
if ser, err = d.GetSerialById(id); chk.E(err) {
|
||||
return
|
||||
@@ -29,25 +29,6 @@ func (d *D) QueryForSerials(c context.Context, f *filter.F) (
|
||||
return
|
||||
}
|
||||
idPkTs = append(idPkTs, tmp...)
|
||||
|
||||
// // fetch the events full id indexes so we can sort them
|
||||
// for _, ser := range founds {
|
||||
// // scan for the IdPkTs
|
||||
// var fidpk *store.IdPkTs
|
||||
// if fidpk, err = d.GetFullIdPubkeyBySerial(ser); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
// if fidpk == nil {
|
||||
// continue
|
||||
// }
|
||||
// idPkTs = append(idPkTs, fidpk)
|
||||
// // sort by timestamp
|
||||
// sort.Slice(
|
||||
// idPkTs, func(i, j int) bool {
|
||||
// return idPkTs[i].Ts > idPkTs[j].Ts
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
} else {
|
||||
if idPkTs, err = d.QueryForIds(c, f); chk.E(err) {
|
||||
return
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"interfaces.orly/store"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/errorf"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// SaveEvent saves an event to the database, generating all the necessary indexes.
|
||||
@@ -71,16 +72,6 @@ func (d *D) SaveEvent(
|
||||
return
|
||||
}
|
||||
idPkTss = append(idPkTss, tmp...)
|
||||
// for _, ser := range sers {
|
||||
// var fidpk *store.IdPkTs
|
||||
// if fidpk, err = d.GetFullIdPubkeyBySerial(ser); chk.E(err) {
|
||||
// return
|
||||
// }
|
||||
// if fidpk == nil {
|
||||
// continue
|
||||
// }
|
||||
// idPkTss = append(idPkTss, fidpk)
|
||||
// }
|
||||
// sort by timestamp, so the first is the newest
|
||||
sort.Slice(
|
||||
idPkTss, func(i, j int) bool {
|
||||
@@ -177,6 +168,6 @@ func (d *D) SaveEvent(
|
||||
return
|
||||
},
|
||||
)
|
||||
// log.T.F("total data written: %d bytes keys %d bytes values", kc, vc)
|
||||
log.T.F("total data written: %d bytes keys %d bytes values", kc, vc)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ const L = "REQ"
|
||||
// newly received events after it returns an eoseenvelope.T.
|
||||
type T struct {
|
||||
Subscription []byte
|
||||
Filters filter.S
|
||||
Filters *filter.S
|
||||
}
|
||||
|
||||
var _ codec.Envelope = (*T)(nil)
|
||||
@@ -32,14 +32,14 @@ func New() *T { return new(T) }
|
||||
|
||||
// NewFrom creates a new reqenvelope.T with a provided subscription.Id and
|
||||
// filters.T.
|
||||
func NewFrom(id []byte, ff filter.S) *T {
|
||||
func NewFrom(id []byte, ff *filter.S) *T {
|
||||
return &T{
|
||||
Subscription: id,
|
||||
Filters: ff,
|
||||
}
|
||||
}
|
||||
|
||||
func NewWithId[V string | []byte](id V, ff filter.S) (sub *T) {
|
||||
func NewWithId[V string | []byte](id V, ff *filter.S) (sub *T) {
|
||||
return &T{
|
||||
Subscription: []byte(id),
|
||||
Filters: ff,
|
||||
@@ -69,7 +69,7 @@ func (en *T) Marshal(dst []byte) (b []byte) {
|
||||
o = append(o, '"')
|
||||
o = append(o, en.Subscription...)
|
||||
o = append(o, '"')
|
||||
for _, f := range en.Filters {
|
||||
for _, f := range *en.Filters {
|
||||
o = append(o, ',')
|
||||
o = f.Marshal(o)
|
||||
}
|
||||
@@ -90,6 +90,7 @@ func (en *T) Unmarshal(b []byte) (r []byte, err error) {
|
||||
if r, err = text.Comma(r); chk.E(err) {
|
||||
return
|
||||
}
|
||||
en.Filters = new(filter.S)
|
||||
if r, err = en.Filters.Unmarshal(r); chk.E(err) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ func (ev *E) MarshalBinary(w io.Writer) {
|
||||
varint.Encode(w, uint64(ev.CreatedAt))
|
||||
varint.Encode(w, uint64(ev.Kind))
|
||||
varint.Encode(w, uint64(ev.Tags.Len()))
|
||||
for _, x := range ev.Tags.ToSliceOfTags() {
|
||||
for _, x := range *ev.Tags {
|
||||
varint.Encode(w, uint64(x.Len()))
|
||||
for _, y := range x.ToSliceOfBytes() {
|
||||
for _, y := range x.T {
|
||||
varint.Encode(w, uint64(len(y)))
|
||||
_, _ = w.Write(y)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoders.orly/hex"
|
||||
"encoders.orly/ints"
|
||||
"encoders.orly/text"
|
||||
"lol.mleku.dev/log"
|
||||
)
|
||||
|
||||
// ToCanonical converts the event to the canonical encoding used to derive the
|
||||
@@ -14,14 +15,15 @@ func (ev *E) ToCanonical(dst []byte) (b []byte) {
|
||||
b = append(b, "[0,\""...)
|
||||
b = hex.EncAppend(b, ev.Pubkey)
|
||||
b = append(b, "\","...)
|
||||
b = ints.New(ev.CreatedAt).Marshal(nil)
|
||||
b = ints.New(ev.CreatedAt).Marshal(b)
|
||||
b = append(b, ',')
|
||||
b = ints.New(ev.Kind).Marshal(nil)
|
||||
b = ints.New(ev.Kind).Marshal(b)
|
||||
b = append(b, ',')
|
||||
b = ev.Tags.Marshal(b)
|
||||
b = append(b, ',')
|
||||
b = text.AppendQuote(b, ev.Content, text.NostrEscape)
|
||||
b = append(b, ']')
|
||||
log.D.F("canonical: %s", b)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"crypto.orly/ec/schnorr"
|
||||
"crypto.orly/sha256"
|
||||
"encoders.orly/event"
|
||||
"encoders.orly/ints"
|
||||
"encoders.orly/kind"
|
||||
"encoders.orly/tag"
|
||||
@@ -100,6 +101,50 @@ func (f *F) Sort() {
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesIgnoringTimestampConstraints checks a filter against an event and
|
||||
// determines if the event matches the filter, ignoring timestamp constraints..
|
||||
func (f *F) MatchesIgnoringTimestampConstraints(ev *event.E) bool {
|
||||
if ev == nil {
|
||||
return false
|
||||
}
|
||||
if f.Ids.Len() > 0 && !f.Ids.Contains(ev.ID) {
|
||||
return false
|
||||
}
|
||||
if f.Kinds.Len() > 0 && !f.Kinds.Contains(ev.Kind) {
|
||||
return false
|
||||
}
|
||||
if f.Authors.Len() > 0 && !f.Authors.Contains(ev.Pubkey) {
|
||||
return false
|
||||
}
|
||||
if f.Tags.Len() > 0 {
|
||||
for _, v := range *f.Tags {
|
||||
if v.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
key := v.Key()
|
||||
values := v.T[1:]
|
||||
if !ev.Tags.ContainsAny(key, values) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Matches checks a filter against an event and determines if the event matches the filter.
|
||||
func (f *F) Matches(ev *event.E) (match bool) {
|
||||
if !f.MatchesIgnoringTimestampConstraints(ev) {
|
||||
return
|
||||
}
|
||||
if f.Since.Int() != 0 && ev.CreatedAt < f.Since.I64() {
|
||||
return
|
||||
}
|
||||
if f.Until.Int() != 0 && ev.CreatedAt > f.Until.I64() {
|
||||
return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Marshal a filter into raw JSON bytes, minified. The field ordering and sort
|
||||
// of fields is canonicalized so that a hash can identify the same filter.
|
||||
func (f *F) Marshal(dst []byte) (b []byte) {
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"encoders.orly/event"
|
||||
"lol.mleku.dev/errorf"
|
||||
)
|
||||
|
||||
type S []*F
|
||||
|
||||
// Match checks if a set of filters.T matches on an event.F.
|
||||
func (s *S) Match(event *event.E) bool {
|
||||
for _, f := range *s {
|
||||
if f.Matches(event) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Marshal encodes a slice of filters as a JSON array of objects.
|
||||
// It appends the result to dst and returns the resulting slice.
|
||||
func (s S) Marshal(dst []byte) (b []byte) {
|
||||
@@ -43,7 +54,7 @@ func (s *S) Unmarshal(b []byte) (r []byte, err error) {
|
||||
if len(r) == 0 {
|
||||
return
|
||||
}
|
||||
f := new(F)
|
||||
f := New()
|
||||
var rem []byte
|
||||
if rem, err = f.Unmarshal(r); err != nil {
|
||||
return
|
||||
|
||||
@@ -59,11 +59,11 @@ func (k *K) ToU64() uint64 {
|
||||
func (k *K) Name() string { return GetString(k.K) }
|
||||
|
||||
// Equal checks if
|
||||
func (k *K) Equal(k2 *K) bool {
|
||||
if k == nil || k2 == nil {
|
||||
func (k *K) Equal(k2 uint16) bool {
|
||||
if k == nil {
|
||||
return false
|
||||
}
|
||||
return k.K == k2.K
|
||||
return k.K == k2
|
||||
}
|
||||
|
||||
var Privileged = []*K{
|
||||
@@ -80,7 +80,7 @@ var Privileged = []*K{
|
||||
// the pubkeys in the event and p tags of the event are party to.
|
||||
func (k *K) IsPrivileged() (is bool) {
|
||||
for i := range Privileged {
|
||||
if k.Equal(Privileged[i]) {
|
||||
if k.Equal(Privileged[i].K) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (k *S) Clone() (c *S) {
|
||||
// Even if a custom number is found, this codebase does not have the logic to
|
||||
// deal with the kind so such a search is pointless and for which reason static
|
||||
// typing always wins. No mistakes possible with known quantities.
|
||||
func (k *S) Contains(s *K) bool {
|
||||
func (k *S) Contains(s uint16) bool {
|
||||
for i := range k.K {
|
||||
if k.K[i].Equal(s) {
|
||||
return true
|
||||
|
||||
52
pkg/encoders/reason/reason.go
Normal file
52
pkg/encoders/reason/reason.go
Normal file
@@ -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...))
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"encoders.orly/text"
|
||||
"lol.mleku.dev/errorf"
|
||||
utils "utils.orly"
|
||||
"utils.orly/bufpool"
|
||||
)
|
||||
|
||||
@@ -67,21 +68,28 @@ func (t *T) Less(i, j int) bool {
|
||||
|
||||
func (t *T) Swap(i, j int) { t.T[i], t.T[j] = t.T[j], t.T[i] }
|
||||
|
||||
func (t *T) ToSliceOfBytes() (b [][]byte) {
|
||||
return t.T
|
||||
// Contains returns true if the provided element is found in the tag slice.
|
||||
func (t *T) Contains(s []byte) (b bool) {
|
||||
for i := range t.T {
|
||||
if utils.FastEqual(t.T[i], s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Marshal encodes a tag.T as standard minified JSON array of strings.
|
||||
func (t *T) Marshal(dst []byte) (b []byte) {
|
||||
dst = append(dst, '[')
|
||||
b = dst
|
||||
b = append(b, '[')
|
||||
for i, s := range t.T {
|
||||
dst = text.AppendQuote(dst, s, text.NostrEscape)
|
||||
b = text.AppendQuote(b, s, text.NostrEscape)
|
||||
if i < len(t.T)-1 {
|
||||
dst = append(dst, ',')
|
||||
b = append(b, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
b = append(b, ']')
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalJSON encodes a tag.T as standard minified JSON array of strings.
|
||||
|
||||
@@ -44,17 +44,26 @@ func (s *S) Append(t ...*T) {
|
||||
*s = append(*s, t...)
|
||||
}
|
||||
|
||||
func (s *S) ToSliceOfTags() (t []T) {
|
||||
if s == nil {
|
||||
return
|
||||
// ContainsAny returns true if any of the values given in `values` matches any
|
||||
// of the tag elements.
|
||||
func (s *S) ContainsAny(tagName []byte, values [][]byte) bool {
|
||||
if len(tagName) < 1 {
|
||||
return false
|
||||
}
|
||||
for _, tt := range *s {
|
||||
if tt == nil {
|
||||
for _, v := range *s {
|
||||
if v.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
t = append(t, *tt)
|
||||
if !utils.FastEqual(v.Key(), tagName) {
|
||||
continue
|
||||
}
|
||||
for _, candidate := range values {
|
||||
if bytes.HasPrefix(v.Value(), candidate) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// MarshalJSON encodes a tags.T appended to a provided byte slice in JSON form.
|
||||
@@ -74,7 +83,8 @@ func (s *S) MarshalJSON() (b []byte, err error) {
|
||||
}
|
||||
|
||||
func (s *S) Marshal(dst []byte) (b []byte) {
|
||||
b = append(dst, '[')
|
||||
b = dst
|
||||
b = append(b, '[')
|
||||
for i, ss := range *s {
|
||||
b = ss.Marshal(b)
|
||||
if i < len(*s)-1 {
|
||||
|
||||
@@ -4,9 +4,34 @@ go 1.25.0
|
||||
|
||||
replace (
|
||||
crypto.orly => ../crypto
|
||||
database.orly => ../database
|
||||
encoders.orly => ../encoders
|
||||
interfaces.orly => ../interfaces
|
||||
next.orly.dev => ../../
|
||||
protocol.orly => ../protocol
|
||||
utils.orly => ../utils
|
||||
)
|
||||
|
||||
require (
|
||||
database.orly v0.0.0-00010101000000-000000000000
|
||||
encoders.orly v0.0.0-00010101000000-000000000000
|
||||
next.orly.dev v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
require (
|
||||
crypto.orly v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/templexxx/cpu v0.0.1 // indirect
|
||||
github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b // indirect
|
||||
go-simpler.org/env v0.12.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
lol.mleku.dev v1.0.2 // indirect
|
||||
lukechampine.com/frand v1.5.1 // indirect
|
||||
utils.orly v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
33
pkg/interfaces/go.sum
Normal file
33
pkg/interfaces/go.sum
Normal file
@@ -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=
|
||||
14
pkg/interfaces/publisher/publisher.go
Normal file
14
pkg/interfaces/publisher/publisher.go
Normal file
@@ -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
|
||||
10
pkg/interfaces/typer/typer.go
Normal file
10
pkg/interfaces/typer/typer.go
Normal file
@@ -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
|
||||
}
|
||||
38
pkg/protocol/publish/publisher.go
Normal file
38
pkg/protocol/publish/publisher.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
19
pkg/utils/atomic/.codecov.yml
Normal file
19
pkg/utils/atomic/.codecov.yml
Normal file
@@ -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/
|
||||
130
pkg/utils/atomic/CHANGELOG.md
Normal file
130
pkg/utils/atomic/CHANGELOG.md
Normal file
@@ -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
|
||||
19
pkg/utils/atomic/LICENSE
Normal file
19
pkg/utils/atomic/LICENSE
Normal file
@@ -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.
|
||||
79
pkg/utils/atomic/Makefile
Normal file
79
pkg/utils/atomic/Makefile
Normal file
@@ -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." )
|
||||
33
pkg/utils/atomic/README.md
Normal file
33
pkg/utils/atomic/README.md
Normal file
@@ -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).
|
||||
45
pkg/utils/atomic/assert_test.go
Normal file
45
pkg/utils/atomic/assert_test.go
Normal file
@@ -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...)
|
||||
}
|
||||
88
pkg/utils/atomic/bool.go
Normal file
88
pkg/utils/atomic/bool.go
Normal file
@@ -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
|
||||
}
|
||||
53
pkg/utils/atomic/bool_ext.go
Normal file
53
pkg/utils/atomic/bool_ext.go
Normal file
@@ -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())
|
||||
}
|
||||
150
pkg/utils/atomic/bool_test.go
Normal file
150
pkg/utils/atomic/bool_test.go
Normal file
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
59
pkg/utils/atomic/bytes.go
Normal file
59
pkg/utils/atomic/bytes.go
Normal file
@@ -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)
|
||||
}
|
||||
56
pkg/utils/atomic/bytes_ext.go
Normal file
56
pkg/utils/atomic/bytes_ext.go
Normal file
@@ -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
|
||||
}
|
||||
252
pkg/utils/atomic/bytes_test.go
Normal file
252
pkg/utils/atomic/bytes_test.go
Normal file
@@ -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()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
23
pkg/utils/atomic/doc.go
Normal file
23
pkg/utils/atomic/doc.go
Normal file
@@ -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
|
||||
89
pkg/utils/atomic/duration.go
Normal file
89
pkg/utils/atomic/duration.go
Normal file
@@ -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
|
||||
}
|
||||
40
pkg/utils/atomic/duration_ext.go
Normal file
40
pkg/utils/atomic/duration_ext.go
Normal file
@@ -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()
|
||||
}
|
||||
73
pkg/utils/atomic/duration_test.go
Normal file
73
pkg/utils/atomic/duration_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
72
pkg/utils/atomic/error.go
Normal file
72
pkg/utils/atomic/error.go
Normal file
@@ -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)))
|
||||
}
|
||||
39
pkg/utils/atomic/error_ext.go
Normal file
39
pkg/utils/atomic/error_ext.go
Normal file
@@ -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
|
||||
}
|
||||
136
pkg/utils/atomic/error_test.go
Normal file
136
pkg/utils/atomic/error_test.go
Normal file
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
42
pkg/utils/atomic/example_test.go
Normal file
42
pkg/utils/atomic/example_test.go
Normal file
@@ -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
|
||||
}
|
||||
77
pkg/utils/atomic/float32.go
Normal file
77
pkg/utils/atomic/float32.go
Normal file
@@ -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
|
||||
}
|
||||
76
pkg/utils/atomic/float32_ext.go
Normal file
76
pkg/utils/atomic/float32_ext.go
Normal file
@@ -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)
|
||||
}
|
||||
73
pkg/utils/atomic/float32_test.go
Normal file
73
pkg/utils/atomic/float32_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
77
pkg/utils/atomic/float64.go
Normal file
77
pkg/utils/atomic/float64.go
Normal file
@@ -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
|
||||
}
|
||||
76
pkg/utils/atomic/float64_ext.go
Normal file
76
pkg/utils/atomic/float64_ext.go
Normal file
@@ -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)
|
||||
}
|
||||
73
pkg/utils/atomic/float64_test.go
Normal file
73
pkg/utils/atomic/float64_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
27
pkg/utils/atomic/gen.go
Normal file
27
pkg/utils/atomic/gen.go
Normal file
@@ -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
|
||||
109
pkg/utils/atomic/int32.go
Normal file
109
pkg/utils/atomic/int32.go
Normal file
@@ -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)
|
||||
}
|
||||
82
pkg/utils/atomic/int32_test.go
Normal file
82
pkg/utils/atomic/int32_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
})
|
||||
}
|
||||
109
pkg/utils/atomic/int64.go
Normal file
109
pkg/utils/atomic/int64.go
Normal file
@@ -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)
|
||||
}
|
||||
82
pkg/utils/atomic/int64_test.go
Normal file
82
pkg/utils/atomic/int64_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
})
|
||||
}
|
||||
116
pkg/utils/atomic/internal/gen-atomicint/main.go
Normal file
116
pkg/utils/atomic/internal/gen-atomicint/main.go
Normal file
@@ -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"))
|
||||
)
|
||||
117
pkg/utils/atomic/internal/gen-atomicint/wrapper.tmpl
Normal file
117
pkg/utils/atomic/internal/gen-atomicint/wrapper.tmpl
Normal file
@@ -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 }}
|
||||
}
|
||||
203
pkg/utils/atomic/internal/gen-atomicwrapper/main.go
Normal file
203
pkg/utils/atomic/internal/gen-atomicwrapper/main.go
Normal file
@@ -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"))
|
||||
)
|
||||
120
pkg/utils/atomic/internal/gen-atomicwrapper/wrapper.tmpl
Normal file
120
pkg/utils/atomic/internal/gen-atomicwrapper/wrapper.tmpl
Normal file
@@ -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 }}
|
||||
35
pkg/utils/atomic/nocmp.go
Normal file
35
pkg/utils/atomic/nocmp.go
Normal file
@@ -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()
|
||||
164
pkg/utils/atomic/nocmp_test.go
Normal file
164
pkg/utils/atomic/nocmp_test.go
Normal file
@@ -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")
|
||||
}
|
||||
100
pkg/utils/atomic/pointer_test.go
Normal file
100
pkg/utils/atomic/pointer_test.go
Normal file
@@ -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.")
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
289
pkg/utils/atomic/stress_test.go
Normal file
289
pkg/utils/atomic/stress_test.go
Normal file
@@ -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{})
|
||||
}
|
||||
}
|
||||
72
pkg/utils/atomic/string.go
Normal file
72
pkg/utils/atomic/string.go
Normal file
@@ -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)))
|
||||
}
|
||||
54
pkg/utils/atomic/string_ext.go
Normal file
54
pkg/utils/atomic/string_ext.go
Normal file
@@ -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
|
||||
}
|
||||
170
pkg/utils/atomic/string_test.go
Normal file
170
pkg/utils/atomic/string_test.go
Normal file
@@ -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"))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
55
pkg/utils/atomic/time.go
Normal file
55
pkg/utils/atomic/time.go
Normal file
@@ -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))
|
||||
}
|
||||
36
pkg/utils/atomic/time_ext.go
Normal file
36
pkg/utils/atomic/time_ext.go
Normal file
@@ -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{}
|
||||
}
|
||||
86
pkg/utils/atomic/time_test.go
Normal file
86
pkg/utils/atomic/time_test.go
Normal file
@@ -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())
|
||||
}
|
||||
30
pkg/utils/atomic/tools/tools.go
Normal file
30
pkg/utils/atomic/tools/tools.go
Normal file
@@ -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"
|
||||
)
|
||||
109
pkg/utils/atomic/uint32.go
Normal file
109
pkg/utils/atomic/uint32.go
Normal file
@@ -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)
|
||||
}
|
||||
77
pkg/utils/atomic/uint32_test.go
Normal file
77
pkg/utils/atomic/uint32_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
109
pkg/utils/atomic/uint64.go
Normal file
109
pkg/utils/atomic/uint64.go
Normal file
@@ -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)
|
||||
}
|
||||
77
pkg/utils/atomic/uint64_test.go
Normal file
77
pkg/utils/atomic/uint64_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
109
pkg/utils/atomic/uintptr.go
Normal file
109
pkg/utils/atomic/uintptr.go
Normal file
@@ -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)
|
||||
}
|
||||
80
pkg/utils/atomic/uintptr_test.go
Normal file
80
pkg/utils/atomic/uintptr_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
}
|
||||
65
pkg/utils/atomic/unsafe_pointer.go
Normal file
65
pkg/utils/atomic/unsafe_pointer.go
Normal file
@@ -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)
|
||||
}
|
||||
83
pkg/utils/atomic/unsafe_pointer_test.go
Normal file
83
pkg/utils/atomic/unsafe_pointer_test.go
Normal file
@@ -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.")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
31
pkg/utils/atomic/value.go
Normal file
31
pkg/utils/atomic/value.go
Normal file
@@ -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
|
||||
}
|
||||
40
pkg/utils/atomic/value_test.go
Normal file
40
pkg/utils/atomic/value_test.go
Normal file
@@ -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") })
|
||||
}
|
||||
@@ -4,16 +4,28 @@ go 1.25.0
|
||||
|
||||
require (
|
||||
encoders.orly v0.0.0-00010101000000-000000000000
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/lint v0.0.0-20241112194109-818c5a804067
|
||||
honnef.co/go/tools v0.6.1
|
||||
lol.mleku.dev v1.0.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
|
||||
@@ -1,16 +1,57 @@
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
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=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
|
||||
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA=
|
||||
golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
|
||||
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
|
||||
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=
|
||||
|
||||
2
pkg/utils/interrupt/README.md
Normal file
2
pkg/utils/interrupt/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# interrupt
|
||||
Handle shutdowns cleanly and enable hot reload
|
||||
153
pkg/utils/interrupt/main.go
Normal file
153
pkg/utils/interrupt/main.go
Normal file
@@ -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()
|
||||
}
|
||||
26
pkg/utils/interrupt/restart.go
Normal file
26
pkg/utils/interrupt/restart.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
20
pkg/utils/interrupt/restart_darwin.go
Normal file
20
pkg/utils/interrupt/restart_darwin.go
Normal file
@@ -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)
|
||||
}
|
||||
20
pkg/utils/interrupt/restart_windows.go
Normal file
20
pkg/utils/interrupt/restart_windows.go
Normal file
@@ -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)
|
||||
}
|
||||
12
pkg/utils/interrupt/sigterm.go
Normal file
12
pkg/utils/interrupt/sigterm.go
Normal file
@@ -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
Reference in New Issue
Block a user