diff --git a/.travis.yml b/.travis.yml index cb90135..ea613a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,9 @@ language: go go: - - 1.9.x - - 1.10.x - - 1.11.x - - 1.12.x - - 1.13.x + - 1.16.x - master + - tip before_install: - go get golang.org/x/tools/cmd/cover diff --git a/README.md b/README.md index f73d570..e67cc09 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ # Monoton [![Build Status](https://travis-ci.org/mustafaturan/monoton.svg?branch=master)](https://travis-ci.org/mustafaturan/monoton) -[![Coverage Status](https://coveralls.io/repos/github/mustafaturan/monoton/badge.svg?branch=master)](https://coveralls.io/github/mustafaturan/monoton?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/mustafaturan/monoton/badge.svg?branch=master)](https://coveralls.io/github/mustafaturan/monoton?branch=main) [![Go Report Card](https://goreportcard.com/badge/github.com/mustafaturan/monoton)](https://goreportcard.com/report/github.com/mustafaturan/monoton) -[![GoDoc](https://godoc.org/github.com/mustafaturan/monoton?status.svg)](https://godoc.org/github.com/mustafaturan/monoton) +[![GoDoc](https://godoc.org/github.com/mustafaturan/monoton?status.svg)](https://godoc.org/github.com/mustafaturan/monoton/v2) Highly scalable, single/multi node, predictable and incremental unique id -generator. +generator with zero allocation magic. ## Installation Via go packages: -```go get github.com/mustafaturan/monoton``` +```go get github.com/mustafaturan/monoton/v2``` ## API The method names and arities/args are stable now. No change should be expected -on the package for the version `1.x.x` except any bug fixes. +on the package for the version `2.x.x` except any bug fixes. ## Usage ### Using with Singleton -Create a new package like below, and then call `Next()` method: +Create a new package like below, and then call `Next()` or `NextBytes()` method: ```go package uniqid @@ -30,18 +30,18 @@ package uniqid // Import packages import ( "fmt" - "github.com/mustafaturan/monoton" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2" + "github.com/mustafaturan/monoton/v2/sequencer" ) var m monoton.Monoton // On init configure the monoton func init() { - m = *(newIDGenerator()) + m = newIDGenerator() } -func newIDGenerator() *monoton.Monoton { +func newIDGenerator() monoton.Monoton { // Fetch your node id from a config server or generate from MAC/IP address node := uint64(1) @@ -64,6 +64,10 @@ func newIDGenerator() *monoton.Monoton { func Generate() string { m.Next() } + +func GeneateBytes() [16]byte { + m.NextBytes() +} ``` In any other package generate the ids like below: @@ -89,8 +93,8 @@ package main // Import packages import ( "fmt" - "github.com/mustafaturan/monoton" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2" + "github.com/mustafaturan/monoton/v2/sequencer" ) func NewIDGenerator() *monoton.Monoton { @@ -179,6 +183,25 @@ consequences. The sequencers can be extended for any other time format, sequence format by implementing the `monoton/sequencer.Sequencer` interface. +## Benchmarks + +Command: +``` +go test -benchtime 10000000x -benchmem -run=^$ -bench=. github.com/mustafaturan/monoton/v2 +``` + +Results: +``` +goos: darwin +goarch: amd64 +pkg: github.com/mustafaturan/monoton/v2 +cpu: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz +BenchmarkNext-4 10000000 121.4 ns/op 0 B/op 0 allocs/op +BenchmarkNextBytes-4 10000000 115.5 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/mustafaturan/monoton/v2 2.639s +``` + ## Contributing All contributors should follow [Contributing Guidelines](CONTRIBUTING.md) and diff --git a/benchmark_test.go b/benchmark_test.go new file mode 100644 index 0000000..58640cb --- /dev/null +++ b/benchmark_test.go @@ -0,0 +1,26 @@ +package monoton_test + +import ( + "testing" + + "github.com/mustafaturan/monoton/v2" + "github.com/mustafaturan/monoton/v2/sequencer" +) + +func BenchmarkNext(b *testing.B) { + b.ReportAllocs() + + m, _ := monoton.New(sequencer.NewMillisecond(), 0, 0) + for n := 0; n < b.N; n++ { + _ = m.Next() + } +} + +func BenchmarkNextBytes(b *testing.B) { + b.ReportAllocs() + + m, _ := monoton.New(sequencer.NewMillisecond(), 0, 0) + for n := 0; n < b.N; n++ { + _ = m.NextBytes() + } +} diff --git a/encoder/encoder.go b/encoder/encoder.go index 289b8b0..08a258f 100644 --- a/encoder/encoder.go +++ b/encoder/encoder.go @@ -1,4 +1,4 @@ -// Copyright 2019 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -7,20 +7,17 @@ // package encoder -import ( - "fmt" - "strconv" -) - const ( maxBase62 = uint64(62) mapping = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ) -// ToBase62 converts int types to Base62 encoded string -func ToBase62(u uint64) string { - var a [65]byte // 64 + 1: +1 for sign of 64bit value in base 2 - i := len(a) +// ToBase62WithPaddingZeros converts int types to Base62 encoded byte array +// with padding zeros +func ToBase62WithPaddingZeros(u uint64, length int) []byte { + const size = 65 // 64 + 1: +1 for sign of 64bit value in base 2 + var a [size]byte + i := size for u >= maxBase62 { i-- // Avoid using r = a%b in addition to q = a/maxBase62 @@ -33,12 +30,21 @@ func ToBase62(u uint64) string { // when u < maxBase62 i-- a[i] = mapping[u] - return string(a[i:]) + for i > size-length { + i-- + a[i] = mapping[0] + } + return a[i:] } -// ToBase62WithPaddingZeros converts int types to Base62 encoded string with -// padding zeros -func ToBase62WithPaddingZeros(u uint64, padding int64) string { - formatter := "%+0" + strconv.FormatInt(padding, 10) + "s" - return fmt.Sprintf(formatter, ToBase62(u)) +// Base62ByteSize returns the minimum byte size length requirement to allocate +// the given unsigned integer's value +func Base62ByteSize(u uint64) int { + i := 0 + for u >= maxBase62 { + i++ + q := u / maxBase62 + u = q + } + return i + 1 } diff --git a/encoder/encoder_test.go b/encoder/encoder_test.go index b8515b7..3229b19 100644 --- a/encoder/encoder_test.go +++ b/encoder/encoder_test.go @@ -2,41 +2,46 @@ package encoder import "testing" -func TestToBase62(t *testing.T) { +func TestToBase62WithPaddingZeros(t *testing.T) { tests := []struct { - val uint64 - want string + val uint64 + padding int + want string }{ - {1, "1"}, - {63, "11"}, - {1<<64 - 1, "LygHa16AHYF"}, + {1, 11, "00000000001"}, + {63, 2, "11"}, + {124, 3, "020"}, + {125, 4, "0021"}, + {1<<64 - 1, 11, "LygHa16AHYF"}, } - msg := "ToBase62(%d) = %v, but returned %v" + msg := "ToBase62WithPaddingZeros(%d, %d) = %v, but returned %v" for _, test := range tests { - got := ToBase62(test.val) - if got != test.want { - t.Errorf(msg, test.val, test.want, got) + got := ToBase62WithPaddingZeros(test.val, test.padding) + if string(got) != test.want { + t.Errorf(msg, test.val, test.padding, test.want, string(got)) } } } -func TestToBase62WithPaddingZeros(t *testing.T) { +func TestBase62ByteSize(t *testing.T) { tests := []struct { - val uint64 - padding int64 - want string + val uint64 + want int }{ - {1, 11, "00000000001"}, - {63, 11, "00000000011"}, - {1<<64 - 1, 11, "LygHa16AHYF"}, + {0, 1}, + {1, 1}, + {63, 2}, + {124, 2}, + {125, 2}, + {1<<64 - 1, 11}, } - msg := "ToBase62WithPaddingZeros(%d, %d) = %v, but returned %v" + msg := "Base62ByteSize(%d) = %d, but returned %v" for _, test := range tests { - got := ToBase62WithPaddingZeros(test.val, test.padding) + got := Base62ByteSize(test.val) if got != test.want { - t.Errorf(msg, test.val, test.padding, test.want, got) + t.Errorf(msg, test.val, test.want, got) } } } diff --git a/example_test.go b/example_test.go index b1e5f3b..d80f8de 100644 --- a/example_test.go +++ b/example_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/mustafaturan/monoton" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2" + "github.com/mustafaturan/monoton/v2/sequencer" ) func ExampleNew() { diff --git a/go.mod b/go.mod index 732ecbc..ed50bb6 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/mustafaturan/monoton +module github.com/mustafaturan/monoton/v2 -go 1.9 +go 1.16 diff --git a/monoton.go b/monoton.go index c549622..54da122 100644 --- a/monoton.go +++ b/monoton.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -60,8 +60,8 @@ Example using Singleton // Import packages import ( "fmt" - "github.com/mustafaturan/monoton" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2" + "github.com/mustafaturan/monoton/v2/sequencer" ) const year2020asMillisecondPST = 1577865600000 @@ -70,10 +70,10 @@ Example using Singleton // On init configure the monoton func init() { - m = *(newIDGenerator()) // to store in the stack + m = newIDGenerator() } - func newIDGenerator() *monoton.Monoton { + func newIDGenerator() monoton.Monoton { // Fetch your node id from a config server or generate from MAC/IP // address node := uint64(1) @@ -119,14 +119,16 @@ import ( "fmt" "math" - "github.com/mustafaturan/monoton/encoder" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2/encoder" + "github.com/mustafaturan/monoton/v2/sequencer" ) const ( totalByteSize = 16 errMaxNode = "node can't be greater than %d (given %d)" - errMaxByteSize = "max byte size sum of sequence(%d) and time sequence(%d) can't be >= total byte size(%d)" + errMaxByteSize = "max byte size sum of sequence(%d) and time sequence(%d) " + + "can't be >= total byte size(%d), " + + "at least 1 byte slot is needed for node" ) // MaxNodeCapacityExceededError is an error type with node information @@ -141,12 +143,9 @@ func (e *MaxNodeCapacityExceededError) Error() string { // MaxByteSizeError is an error type with sequence & time byte sizes type MaxByteSizeError struct { - ByteSizeSequence int64 - ByteSizeSequenceTime int64 - ByteSizeTotal int64 - - MaxSequence string - MaxSequenceTime string + ByteSizeSequence int + ByteSizeSequenceTime int + ByteSizeTotal int } func (e *MaxByteSizeError) Error() string { @@ -160,64 +159,86 @@ func (e *MaxByteSizeError) Error() string { // Monoton is a sequential id generator type Monoton struct { - sequencer *sequencer.Sequencer initialTime uint64 - node string - timeSeqByteSize int64 - seqByteSize int64 + timeSeqByteSize int + seqByteSize int + sequencer sequencer.Sequencer + node []byte } // New inits a new monoton ID generator with the given generator and node. -func New(s sequencer.Sequencer, node, initialTime uint64) (*Monoton, error) { - m := &Monoton{sequencer: &s, initialTime: initialTime} +func New(s sequencer.Sequencer, node, initialTime uint64) (Monoton, error) { + m := Monoton{sequencer: s, initialTime: initialTime} if err := m.configureByteSizes(); err != nil { - return nil, err + return Monoton{}, err } if err := m.configureNode(node); err != nil { - return nil, err + return Monoton{}, err } return m, nil } // Next generates next incremental unique identifier as Base62 -// The execution will return the following Bytes (B) for the known sequencer -// types: +// The execution returns the following Bytes (B) for the known sequencer types: +// +// Second: 16 B => 6 B (seconds) + 6 B (counter) + 4 B (node) +// Millisecond: 16 B => 8 B (milliseconds) + 4 B (counter) + 4 B (node) +// Nanosecond: 16 B => 11 B (nanoseconds) + 2 B (counter) + 3 B (node) +// +// For byte size decisions please refer to docs/adrs/byte-sizes.md +func (m Monoton) Next() string { + val := m.NextBytes() + return string(val[:]) +} + +// NextBytes generates next incremental unique identifier as Base62 16 bytes +// array +// The execution returns the following Bytes (B) for the known sequencer types: // // Second: 16 B => 6 B (seconds) + 6 B (counter) + 4 B (node) // Millisecond: 16 B => 8 B (milliseconds) + 4 B (counter) + 4 B (node) // Nanosecond: 16 B => 11 B (nanoseconds) + 2 B (counter) + 3 B (node) // // For byte size decisions please refer to docs/adrs/byte-sizes.md -func (m *Monoton) Next() string { - t, seq := (*m.sequencer).Next() +func (m Monoton) NextBytes() [16]byte { + t, seq := m.sequencer.Next() + var n [totalByteSize]byte + copy( + n[0:m.timeSeqByteSize], + encoder.ToBase62WithPaddingZeros(t-m.initialTime, m.timeSeqByteSize), + ) + copy( + n[m.timeSeqByteSize:m.timeSeqByteSize+m.seqByteSize], + encoder.ToBase62WithPaddingZeros(seq, m.seqByteSize), + ) + copy( + n[m.timeSeqByteSize+m.seqByteSize:], + m.node, + ) - return encoder.ToBase62WithPaddingZeros(t-m.initialTime, m.timeSeqByteSize) + - encoder.ToBase62WithPaddingZeros(seq, m.seqByteSize) + - m.node + return n } func (m *Monoton) configureByteSizes() error { - sequencer := *m.sequencer - maxSeqTime := encoder.ToBase62(sequencer.MaxTime()) - m.timeSeqByteSize = int64(len(maxSeqTime)) - - maxSeq := encoder.ToBase62(sequencer.Max()) - m.seqByteSize = int64(len(maxSeq)) + // sequencer := m.sequencer + maxTimeSeqByteSize := encoder.Base62ByteSize(m.sequencer.MaxTime()) + maxSeqByteSize := encoder.Base62ByteSize(m.sequencer.Max()) // At least one byte slot is necessary for the node - if m.seqByteSize+m.timeSeqByteSize >= totalByteSize { + if maxTimeSeqByteSize+maxSeqByteSize >= totalByteSize { return &MaxByteSizeError{ - ByteSizeSequence: m.seqByteSize, - ByteSizeSequenceTime: m.timeSeqByteSize, + ByteSizeSequence: maxSeqByteSize, + ByteSizeSequenceTime: maxTimeSeqByteSize, ByteSizeTotal: totalByteSize, - MaxSequence: maxSeq, - MaxSequenceTime: maxSeqTime, } } + m.timeSeqByteSize = maxTimeSeqByteSize + m.seqByteSize = maxSeqByteSize + return nil } @@ -230,7 +251,7 @@ func (m *Monoton) configureNode(node uint64) error { return nil } -func (m *Monoton) validateNode(node uint64) error { +func (m Monoton) validateNode(node uint64) error { maxNode := uint64(math.Pow(62, float64(m.nodeByteSize()))) - 1 if node > maxNode { return &MaxNodeCapacityExceededError{Node: node, MaxNode: maxNode} @@ -239,6 +260,6 @@ func (m *Monoton) validateNode(node uint64) error { return nil } -func (m *Monoton) nodeByteSize() int64 { +func (m Monoton) nodeByteSize() int { return totalByteSize - (m.timeSeqByteSize + m.seqByteSize) } diff --git a/monoton_test.go b/monoton_test.go index d79e151..fc203c9 100644 --- a/monoton_test.go +++ b/monoton_test.go @@ -1,16 +1,17 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. package monoton import ( + "bytes" "errors" "math" "strings" "testing" - "github.com/mustafaturan/monoton/sequencer" + "github.com/mustafaturan/monoton/v2/sequencer" ) func TestNew(t *testing.T) { @@ -52,7 +53,7 @@ func TestNew(t *testing.T) { &invalidSequencer{}, 1, uint64(0), - errors.New("max byte size sum of sequence(8) and time sequence(8) can't be >= total byte size(16)"), + errors.New("max byte size sum of sequence(8) and time sequence(8) can't be >= total byte size(16), at least 1 byte slot is needed for node"), "", uint64(0), }, @@ -66,7 +67,7 @@ func TestNew(t *testing.T) { got, err := New(test.s, test.node, test.initialTime) t.Run("assigns node val correctly", func(t *testing.T) { - if got.node != test.wantNode { + if string(got.node) != test.wantNode { t.Errorf(nodeMsg, test.s, test.node, test.wantNode, got.node) } }) @@ -86,15 +87,10 @@ func TestNew(t *testing.T) { for _, test := range errorTests { test := test - got, err := New(test.s, test.node, test.initialTime) - t.Run("must not initialize", func(t *testing.T) { - if got != nil { - t.Errorf("want nil but got %+v", got) - } - }) + _, err := New(test.s, test.node, test.initialTime) t.Run("errors with correct message", func(t *testing.T) { if err != test.wantErr && err.Error() != test.wantErr.Error() { - t.Errorf(configureMsg, test.s, test.node, test.initialTime, test.wantErr, got) + t.Errorf(configureMsg, test.s, test.node, test.initialTime, test.wantErr, err.Error()) } }) } @@ -122,6 +118,28 @@ func TestNext(t *testing.T) { }) } +func TestNextBytes(t *testing.T) { + m, _ := New(&validSequencer{}, 3843, 0) + m1, m2 := m.NextBytes(), m.NextBytes() + + t.Run("generates greater sequences on each call", func(t *testing.T) { + t.Parallel() + if bytes.Compare(m1[:], m2[:]) >= 0 { + t.Errorf("Next(): %s >= Next(): %s", m1, m2) + } + }) + + t.Run("generates 16 bytes sequences", func(t *testing.T) { + t.Parallel() + results := [][]byte{m1[:], m2[:]} + for _, r := range results { + if len(r) != 16 { + t.Errorf("Next(): %s couldn't produce 16 bytes", r) + } + } + }) +} + type validSequencer struct { counter uint64 } diff --git a/sequencer/millisecond.go b/sequencer/millisecond.go index f3202be..cc37a35 100644 --- a/sequencer/millisecond.go +++ b/sequencer/millisecond.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -7,7 +7,7 @@ package sequencer import ( "time" - "github.com/mustafaturan/monoton/mtimer" + "github.com/mustafaturan/monoton/v2/mtimer" ) // NewMillisecond returns the preconfigured millisecond sequencer diff --git a/sequencer/millisecond_test.go b/sequencer/millisecond_test.go index 06f6f0a..2186cc9 100644 --- a/sequencer/millisecond_test.go +++ b/sequencer/millisecond_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. diff --git a/sequencer/nanosecond.go b/sequencer/nanosecond.go index db8610a..4fe1ded 100644 --- a/sequencer/nanosecond.go +++ b/sequencer/nanosecond.go @@ -1,11 +1,11 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. package sequencer import ( - "github.com/mustafaturan/monoton/mtimer" + "github.com/mustafaturan/monoton/v2/mtimer" ) // NewNanosecond returns the preconfigured nanosecond sequencer diff --git a/sequencer/nanosecond_test.go b/sequencer/nanosecond_test.go index a53c962..0ef832e 100644 --- a/sequencer/nanosecond_test.go +++ b/sequencer/nanosecond_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. diff --git a/sequencer/second.go b/sequencer/second.go index b4e3b1f..0d317d3 100644 --- a/sequencer/second.go +++ b/sequencer/second.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -7,7 +7,7 @@ package sequencer import ( "time" - "github.com/mustafaturan/monoton/mtimer" + "github.com/mustafaturan/monoton/v2/mtimer" ) // NewSecond returns the preconfigured second sequencer diff --git a/sequencer/second_test.go b/sequencer/second_test.go index ed6ed6f..6a2e909 100644 --- a/sequencer/second_test.go +++ b/sequencer/second_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. diff --git a/sequencer/sequence.go b/sequencer/sequence.go index 15eccbf..fa5d1fc 100644 --- a/sequencer/sequence.go +++ b/sequencer/sequence.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -8,7 +8,7 @@ import "sync" // Sequence is an implementation of sequencer type Sequence struct { - sync.Mutex + mutex sync.Mutex current uint64 time uint64 @@ -29,8 +29,8 @@ func (s *Sequence) MaxTime() uint64 { // Next returns the next sequence func (s *Sequence) Next() (uint64, uint64) { - s.Lock() - defer s.Unlock() + s.mutex.Lock() + defer s.mutex.Unlock() s.increment() return s.time, s.current diff --git a/sequencer/sequence_test.go b/sequencer/sequence_test.go index 6d8acb1..e115f64 100644 --- a/sequencer/sequence_test.go +++ b/sequencer/sequence_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/mustafaturan/monoton/mtimer" + "github.com/mustafaturan/monoton/v2/mtimer" ) func TestMax_Sequence(t *testing.T) { diff --git a/sequencer/sequencer.go b/sequencer/sequencer.go index d7a7f7c..1d37773 100644 --- a/sequencer/sequencer.go +++ b/sequencer/sequencer.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file. diff --git a/sequencer/sequencer_test.go b/sequencer/sequencer_test.go index be7bca3..2111b8b 100644 --- a/sequencer/sequencer_test.go +++ b/sequencer/sequencer_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 Mustafa Turan. All rights reserved. +// Copyright 2021 Mustafa Turan. All rights reserved. // Use of this source code is governed by a Apache License 2.0 license that can // be found in the LICENSE file.