-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(operation): Introducing operation entity and thread safe operation queue. #16
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright 2021, athena-crdt authors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package operations | ||
|
||
import ( | ||
"sort" | ||
"sync" | ||
|
||
"github.com/pkg/errors" | ||
|
||
"github.com/athena-crdt/athena-core/operations/defs" | ||
"github.com/athena-crdt/athena-core/operations/lamport" | ||
) | ||
|
||
type ( | ||
// Mutation is the type of operation | ||
Mutation uint8 | ||
|
||
// Operation - individual operation entity | ||
Operation struct { | ||
Id *lamport.Clock | ||
Deps []*lamport.Clock | ||
Cursor []defs.NodeId | ||
Mutation Mutation | ||
Value defs.Node | ||
} | ||
|
||
// OpStore is a thread safe Operation queue. | ||
OpStore struct { | ||
sync.RWMutex | ||
Store []*Operation | ||
} | ||
) | ||
|
||
const ( | ||
ASSIGN = Mutation(iota) | ||
INSERT | ||
DELETE | ||
GET | ||
) | ||
|
||
// NewOpStore creates a new operation store. | ||
func NewOpStore() *OpStore { | ||
return &OpStore{ | ||
RWMutex: sync.RWMutex{}, | ||
Store: []*Operation{}, | ||
} | ||
} | ||
|
||
func (o *Operation) SortDeps() { | ||
sort.Slice(o.Deps, func(i, j int) bool { | ||
return o.Deps[i].GetTime() <= o.Deps[j].GetTime() | ||
}) | ||
} | ||
|
||
func (op *OpStore) Push(o *Operation) { | ||
op.Lock() | ||
defer op.Unlock() | ||
op.Store = append(op.Store, o) | ||
} | ||
|
||
func (op *OpStore) SortedPush(o *Operation) { | ||
op.Lock() | ||
defer op.Unlock() | ||
op.Store = append(op.Store, o) | ||
sort.Slice(op.Store, func(i, j int) bool { | ||
return op.Store[i].Id.GetTime() <= op.Store[j].Id.GetTime() | ||
}) | ||
} | ||
|
||
func (op *OpStore) IsEmpty() bool { | ||
return op.Len() == 0 | ||
} | ||
|
||
func (op *OpStore) Len() int { | ||
op.RLock() | ||
defer op.RUnlock() | ||
return len(op.Store) | ||
} | ||
|
||
func (op *OpStore) Pop() (*Operation, error) { | ||
op.Lock() | ||
defer op.Unlock() | ||
if len(op.Store) == 0 { | ||
return nil, errors.New("empty queue") | ||
} | ||
|
||
elm := op.Store[0] | ||
op.Store = op.Store[1:] | ||
return elm, nil | ||
} | ||
|
||
func (op *OpStore) Sort() { | ||
sort.Slice(op.Store, func(i, j int) bool { | ||
return op.Store[i].Id.GetTime() <= op.Store[j].Id.GetTime() | ||
}) | ||
} | ||
|
||
func (op *OpStore) Serialize() ([]byte, error) { | ||
op.RLock() | ||
defer op.RUnlock() | ||
panic("implement after PR #12") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,29 +14,33 @@ | |
|
||
package lamport | ||
|
||
import ( | ||
"sync/atomic" | ||
) | ||
import "sync/atomic" | ||
|
||
type Clock struct { | ||
// counter represents current lamport counter value. | ||
counter uint64 | ||
// hostId identifies the host where the operation was generated at first place. | ||
hostId string | ||
} | ||
|
||
func (clock *Clock) IsGreaterThan(obj *Clock) bool { | ||
if atomic.LoadUint64(&obj.counter) == atomic.LoadUint64(&clock.counter) { | ||
return clock.hostId > obj.hostId | ||
} else { | ||
return atomic.LoadUint64(&clock.counter) > atomic.LoadUint64(&obj.counter) | ||
// NewClock returns a pointer to a newly created clock. | ||
func NewClock(counter uint64, hostId string) *Clock { | ||
return &Clock{ | ||
hostId: hostId, | ||
counter: counter, | ||
} | ||
} | ||
|
||
func (clock *Clock) IsGreaterThan(obj *Clock) bool { | ||
return atomic.LoadUint64(&clock.counter) > atomic.LoadUint64(&obj.counter) | ||
} | ||
|
||
func (clock *Clock) IsLessThan(obj *Clock) bool { | ||
if atomic.LoadUint64(&obj.counter) == atomic.LoadUint64(&clock.counter) { | ||
return clock.hostId < obj.hostId | ||
} else { | ||
return atomic.LoadUint64(&clock.counter) < atomic.LoadUint64(&obj.counter) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. revert There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (counter, hostId) defines total ordering of operations across all replicas There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just think logically and tell me, how does it differ when the two operations happing at two different hosts have the same Lamport counter. The ordering of the host id should matter? No. Either the host id is auto-generated at init or randomly given during bootstrapping. So why should we take that into consideration inside the programming logic? Just keep it for the identification purpose - like where the counter was set. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To resolve concurrent operations, For LWW to work we will need someone to win.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I understand. We need to think about this constructively...instead of in such a destructive way like the last writer wins. As CRDT is all about resolving conflict optimistically : ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if both have random ids which are coincidentally equal. We'll end up introducing randomness into the system There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. each replica must have a unique id There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can't ensure must. Instead we could focus here
|
||
} | ||
return atomic.LoadUint64(&clock.counter) < atomic.LoadUint64(&obj.counter) | ||
} | ||
|
||
func (clock *Clock) IsEqual(obj *Clock) bool { | ||
return atomic.LoadUint64(&clock.counter) == atomic.LoadUint64(&obj.counter) | ||
} | ||
|
||
func (clock *Clock) Increment() uint64 { | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are we using pointers here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To prevent it from unnecessary being copied and to have the atomicity while checking and updating the counter value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at operation level the clock is fixed as is used as an id so no updates.
Suggestion:
don't use pointers here
rest looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It hardly makes any difference. If you insist - value then.