/// <summary> /// Transfers the data with the key and entry to the local store. /// </summary> /// <param name="entry"></param> /// <param name="value"></param> /// <param name="dht"></param> /// <param name="cancellationToken"></param> /// <returns></returns> async Task TransferAsync(IKsStoreEntry <TKey> entry, KsHashTableValue value, KsHashTableEntry dht, CancellationToken cancellationToken) { foreach (var uri in dht.Endpoints) { // we can't pull the value from ourselves, move to secondaries if (uri == options.Value.Uri) { continue; } var client = clients.Get(uri); if (client == null) { throw new KsException($"Could not obtain client for remote host: '{uri}'"); } // existing token if we're resuming an operation after failure var t = await entry.GetOwnerTokenAsync(cancellationToken); var v = await client.ShiftLockAsync(entry.Key, t, cancellationToken); // current owner returns no value, must not really own it, nothing to transfer if (v == null) { return; } // track down owner by following forwards while (v.Value.ForwardUri != null) { client = clients.Get(v.Value.ForwardUri); v = await client.ShiftLockAsync(entry.Key, t, cancellationToken); } if (v.Value.Data == null) { throw new KsException("Data not retrieved."); } if (v.Value.Data == null) { throw new KsException("Data retrieved, but not token."); } // update local entry with latest information await entry.SetOwnerTokenAsync(t = v.Value.Token); await entry.SetAsync(v.Value.Data); // finalize shift on remote node and publish entry await client.ShiftAsync(entry.Key, t, options.Value.Uri, cancellationToken); await hashtable.AddAsync(entry.Key, new KsHashTableValue(Serialize(new KsHashTableEntry(new[] { options.Value.Uri })), value.Version + 1, TimeSpan.FromDays(1))); // we succeeded, exit loop break; } // zero out lock token if we successfully complete process await entry.SetOwnerTokenAsync(null); }
/// <summary> /// Executes the given function after ensuring ownership of the key. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="key"></param> /// <param name="func"></param> /// <param name="cancellationToken"></param> /// <returns></returns> async Task <T> Access <T>(TKey key, Func <IKsStoreEntry <TKey>, CancellationToken, Task <T> > func, bool shift, CancellationToken cancellationToken) { await using var entry = await store.OpenAsync(key, cancellationToken); var v = await hashtable.GetAsync(key, cancellationToken); // value is unknown to the system if (v == null) { // initial publish of DHT record v = new KsHashTableValue(Serialize(new KsHashTableEntry(new[] { options.Value.Uri })), 1, TimeSpan.FromDays(1)); await hashtable.AddAsync(key, v, cancellationToken); return(await func(entry, cancellationToken)); } // try to transfer object until transfer succeeds if (shift) { var e = Deserialize(v.Value.Data); while (e.Endpoints.Length == 0 || e.Endpoints[0] != options.Value.Uri) { await TransferAsync(entry, v.Value, e, cancellationToken); v = await hashtable.GetAsync(key, cancellationToken); e = Deserialize(v.Value.Data); } } return(await func(entry, cancellationToken)); }