181 lines
6.0 KiB
C++
181 lines
6.0 KiB
C++
/*
|
|
Copyright 2024 Andrew Noyes
|
|
|
|
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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
|
|
#ifdef __cplusplus
|
|
/** A data structure for optimistic concurrency control on ranges of
|
|
* bitwise-lexicographically-ordered keys.
|
|
*
|
|
* Thread safety:
|
|
* - It's safe to operate on two different ConflictSets in two different
|
|
* threads concurrently
|
|
* - It's safe to have multiple threads operating on the same ConflictSet
|
|
* concurrently if and only if all threads only call const methods
|
|
*/
|
|
struct __attribute__((__visibility__("default"))) ConflictSet {
|
|
enum Result {
|
|
/** The result of a check which does not intersect any conflicting writes */
|
|
Commit,
|
|
/** The result of a check which intersects a write at a version >
|
|
readVersion */
|
|
Conflict,
|
|
/** The result of a check with a readVersion older than the highest call to
|
|
`setOldestVersion` */
|
|
TooOld
|
|
};
|
|
|
|
/** Bytes ordered lexicographically */
|
|
struct Key {
|
|
const uint8_t *p;
|
|
int len;
|
|
};
|
|
|
|
/** Denotes a set of keys to be checked for conflicting writes since
|
|
* `readVersion` */
|
|
struct ReadRange {
|
|
Key begin;
|
|
/** `end` having length 0 denotes that this range is the single key {begin}.
|
|
* Otherwise this denotes the range [begin, end) */
|
|
Key end;
|
|
int64_t readVersion;
|
|
};
|
|
|
|
/** Denotes a set of keys to be written at `writeVersion` */
|
|
struct WriteRange {
|
|
Key begin;
|
|
/** `end` having length 0 denotes that this range is the single key {begin}.
|
|
* Otherwise this denotes the range [begin, end) */
|
|
Key end;
|
|
};
|
|
|
|
/** The result of checking reads[i] is written in results[i] */
|
|
void check(const ReadRange *reads, Result *results, int count) const;
|
|
|
|
/** `writes` must be sorted ascending, and must not have adjacent or
|
|
* overlapping ranges. Reads intersecting writes where readVersion <
|
|
* `writeVersion` will result in `Conflict` (or `TooOld`, eventually).
|
|
* `writeVersion` must be greater than every write version in all previous
|
|
* calls to `addWrites` */
|
|
void addWrites(const WriteRange *writes, int count, int64_t writeVersion);
|
|
|
|
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be
|
|
* greater than any previous oldestVersion */
|
|
void setOldestVersion(int64_t oldestVersion);
|
|
|
|
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are
|
|
* no writes initially */
|
|
explicit ConflictSet(int64_t oldestVersion);
|
|
|
|
~ConflictSet();
|
|
|
|
/** Returns the total bytes in use by this ConflictSet */
|
|
int64_t getBytes() const;
|
|
|
|
#if __cplusplus > 199711L
|
|
ConflictSet(ConflictSet &&) noexcept;
|
|
ConflictSet &operator=(ConflictSet &&) noexcept;
|
|
ConflictSet(const ConflictSet &) = delete;
|
|
ConflictSet &operator=(const ConflictSet &) = delete;
|
|
#endif
|
|
|
|
/** @private */
|
|
struct Impl;
|
|
|
|
private:
|
|
Impl *impl;
|
|
};
|
|
|
|
#else
|
|
|
|
/** A data structure for optimistic concurrency control on ranges of
|
|
* bitwise-lexicographically-ordered keys.
|
|
*
|
|
* Thread safety:
|
|
* - It's safe to operate on two different ConflictSets in two different
|
|
* threads concurrently
|
|
* - It's safe to have multiple threads operating on the same ConflictSet
|
|
* concurrently if and only if all threads only call functions that accept a
|
|
* const ConflictSet pointer
|
|
*/
|
|
typedef struct ConflictSet ConflictSet;
|
|
|
|
typedef enum {
|
|
/** The result of a check which does not intersect any conflicting writes */
|
|
ConflictSet_Commit,
|
|
/** The result of a check which intersects a write at a version > readVersion
|
|
*/
|
|
ConflictSet_Conflict,
|
|
/** The result of a check with a readVersion older than the highest call to
|
|
`setOldestVersion` */
|
|
ConflictSet_TooOld
|
|
} ConflictSet_Result;
|
|
|
|
/** Bytes ordered lexicographically */
|
|
typedef struct {
|
|
const uint8_t *p;
|
|
int len;
|
|
} ConflictSet_Key;
|
|
|
|
/** Denotes a set of keys to be checked for conflicting writes since
|
|
* `readVersion` */
|
|
typedef struct {
|
|
ConflictSet_Key begin;
|
|
/** `end` having length 0 denotes that this range is the single key {begin}.
|
|
* Otherwise this denotes the range [begin, end) */
|
|
ConflictSet_Key end;
|
|
int64_t readVersion;
|
|
} ConflictSet_ReadRange;
|
|
|
|
/** Denotes a set of keys to be written at `writeVersion` */
|
|
typedef struct {
|
|
ConflictSet_Key begin;
|
|
/** `end` having length 0 denotes that this range is the single key {begin}.
|
|
* Otherwise this denotes the range [begin, end) */
|
|
ConflictSet_Key end;
|
|
} ConflictSet_WriteRange;
|
|
|
|
/** The result of checking reads[i] is written in results[i] */
|
|
void ConflictSet_check(const ConflictSet *cs,
|
|
const ConflictSet_ReadRange *reads,
|
|
ConflictSet_Result *results, int count);
|
|
|
|
/** `writes` must be sorted ascending, and must not have adjacent or
|
|
* overlapping ranges. Reads intersecting writes where readVersion <
|
|
* `writeVersion` will result in `Conflict` (or `TooOld`, eventually).
|
|
* `writeVersion` must be greater than all write versions in all previous
|
|
* calls to `addWrites` */
|
|
void ConflictSet_addWrites(ConflictSet *cs,
|
|
const ConflictSet_WriteRange *writes, int count,
|
|
int64_t writeVersion);
|
|
|
|
/** Reads where readVersion < oldestVersion will result in `TooOld`. Must be
|
|
* greater than any previous oldestVersion */
|
|
void ConflictSet_setOldestVersion(ConflictSet *cs, int64_t oldestVersion);
|
|
|
|
/** Reads where readVersion < oldestVersion will result in `TooOld`. There are
|
|
* no writes initially */
|
|
ConflictSet *ConflictSet_create(int64_t oldestVersion);
|
|
|
|
void ConflictSet_destroy(ConflictSet *cs);
|
|
|
|
/** Returns the total bytes in use by this ConflictSet */
|
|
int64_t ConflictSet_getBytes(const ConflictSet *cs);
|
|
|
|
#endif
|