Skip to content

Conversation

@yim-lee
Copy link
Member

@yim-lee yim-lee commented Apr 29, 2020

Motivation:
CRDT.ORMap does not conform to ProtobufRepresentable

Modiifications:

  • Modified ORMap so it doesn't require a closure in initializer
  • Conform ORMap and ORMapDelta to ProtobufRepresentable

Result:
Part of #507

Motivation:
`CRDT.ORMap` does not conform to `ProtobufRepresentable`

Modiifications:
- Modified `ORMap` so it doesn't require a closure in initializer
- Conform `ORMap` and `ORMapDelta` to `ProtobufRepresentable`

Result:
Part of #507

message CRDTORMap {
message Delta {
CRDTORMapValue defaultValue = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

carrying the default a bit weird I guess... it's because we want to merge(default, this value) if the other node does not have a value? Would the other node's defaultValue not be usable instead?

Copy link
Member Author

@yim-lee yim-lee Apr 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for merge, update, and reset--more so for update which takes a mutator closure and expects Value as param, so we make a copy of defaultValue in case there is no existing value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can modify the mutator to accept Value? and return Value, so the caller is responsible for providing the default if needed, then we can get rid of this defaultValue I think...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modifications would look like thing(.empty) { $0.add("hello") I guess then right? That's bearable (AFAIR also how Akka's Map worked)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not so easy. We need something for merge in case key doesn't have value locally, and we can't just copy other node's value because the replicaID is expected to be different.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you meant by your first point now. We don't have to transport defaultValue--we would still require it in the initializer, but we allow it to be nil if we got it from remote. All we care about is local ORMap has a defaultValue. The remote one doesn't matter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right yes; I'm not sure how it complicates end user API though 🤔 Curious what you can figure out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does look nicer! :)

/// but it **is** required in the initializer to ensure that **local** `ORMap` has `defaultValue`
/// for `merge`, `update`, etc. In those methods `defaultValue` is required in case **local**
/// `ORMap` does not have an existing value for the given `key`.
let defaultValue: Value?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now optional when constructed from protobuf

}

init(replicaID: ReplicaID, valueInitializer: @escaping () -> Value) {
init(replicaID: ReplicaID, defaultValue: Value) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultValue is required for local construction

}

public mutating func update(key: Key, mutator: (inout Value) -> Void) {
guard let defaultValue = self.defaultValue else {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these guards as a result of making defaultValue optional


// Apply `mutator` to the value then save it to state. Create `Value` if needed.
var value = self._values[key] ?? self.valueInitializer()
var value = self._values[key] ?? defaultValue
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming value semantics

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

let defaultValue: Value?

init(keys: ORSet<Key>.Delta, values: [Key: Value], valueInitializer: @escaping () -> Value) {
init(keys: ORSet<Key>.Delta, values: [Key: Value], defaultValue: Value?) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta may be constructed from deserialized ORMap (https://github.com/apple/swift-distributed-actors/pull/598/files#diff-6d3797019f1301f2492ee9aed777e118R77), which has defaultValue as nil, so here we have to allow Value? not Value.

/// Additional `ORMap` methods when `Value` type conforms to `ResettableCRDT`.
public protocol ORMapWithResettableValue: ORMapWithUnsafeRemove {
/// Resets value for `key` to `defaultValue` provided in `init`.
/// Resets value for `key` by calling `ResettableCRDT.reset()`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected the comments

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// If `k` is not found in `self` then create a new `Value` instance.
// We must NOT copy `other`'s value directly to `self` because the two should have different replica IDs.
var lv: Value = self[k] ?? valueInitializer()
var lv: Value = self[k] ?? defaultValue
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming value semantics

let serialized = try system.serialization.serialize(map)
let deserialized = try system.serialization.deserialize(as: CRDT.ORMap<String, CRDT.ORSet<String>>.self, from: serialized)

"\(deserialized.replicaID)".shouldContain("actor:sact://CRDTSerializationTests@localhost:9001/user/alpha")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this is another one of those localhost to 127.0.0.1 change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok no problem, sorry about the change 🙇 In the end it's more consistent though I think..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end it's more consistent though

+1. 🙏

@yim-lee
Copy link
Member Author

yim-lee commented Apr 29, 2020

Test failure was #463

@yim-lee yim-lee requested a review from ktoso April 29, 2020 18:19
/// - SeeAlso: `CRDT.ORMap`
/// - SeeAlso: `CRDT.LWWRegister`
public struct LWWMap<Key: Codable & Hashable, Value: ActorMessage>: NamedDeltaCRDT, LWWMapOperations {
public struct LWWMap<Key: Codable & Hashable, Value: Codable>: NamedDeltaCRDT, LWWMapOperations {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good 👍

ActorMessage really only a marker for us to grep over them when we : ActorMessage things, Codable in most places is the right thing 👍

@ktoso ktoso added this to the 0.5.0 - Cluster Hardening milestone Apr 30, 2020
Copy link
Member

@ktoso ktoso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit iffy if the optional is good enough as I've not dug very deep here but if there's issues we'll hit them during replication work so let's solve then 👍

LGTM

@ktoso ktoso merged commit b423553 into apple:master Apr 30, 2020
@ktoso ktoso deleted the ormap-seri branch April 30, 2020 02:52
@ktoso ktoso added the t:crdt label Apr 30, 2020
@yim-lee
Copy link
Member Author

yim-lee commented Apr 30, 2020

I'm a bit iffy if the optional is good enough

yeah same. worst case we just add it back to the payload...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants