Document exceptions and other details in storage v2 API

Summary:
Most instances of `@throw std::bad_alloc` are left unexplained as these
functions perform general heap allocations are it's obvious from the
function name that it will do so. Basically anything with `Create`, `Make` or
`Build` implies allocations. Additionally, which parts exactly perform
allocations are an implementation detail. Functions which do unexpected
heap allocations have the reason stated in the documentation, these
functions typically have exactly one spot which could raise such an
exception.

Some functions are marked as `noexcept`, these are usually "special
functions" such as constructors and operators. This could potentially
improve performance because STL may use API overloads that work faster
with `noexcept` stuff. Remaining non-throwing functions aren't marked as
`noexcept` as that wasn't our practice nor is common in our codebase. On
the other hand, if we continue enforcing the documentation of thrown
exceptions, perhaps we should start using `noexcept`.

Reviewers: mferencevic, ipaljak

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2350
This commit is contained in:
Teon Banek 2019-07-31 11:10:19 +02:00
parent 02722a1ed5
commit 9a94205a07
11 changed files with 98 additions and 3 deletions

View File

@ -44,6 +44,7 @@ class CommitLog final {
}
/// Mark a transaction as finished.
/// @throw std::bad_alloc
void MarkFinished(uint64_t id) {
std::lock_guard<utils::SpinLock> guard(lock_);
@ -98,6 +99,7 @@ class CommitLog final {
oldest_active_ = next_start_;
}
/// @throw std::bad_alloc
Block *FindOrCreateBlock(uint64_t id) {
if (!head_) {
head_ = allocator_.allocate(1);

View File

@ -39,8 +39,10 @@ class EdgeAccessor final {
/// @throw std::bad_alloc
Result<bool> SetProperty(PropertyId property, const PropertyValue &value);
/// @throw std::bad_alloc
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
/// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
Gid Gid() const { return edge_->gid; }

View File

@ -110,6 +110,7 @@ bool AnyVersionHasLabel(Vertex *vertex, LabelId label, uint64_t timestamp) {
/// Helper function for label-property index garbage collection. Returns true if
/// there's a reachable version of the vertex that has the given label and
/// property value.
/// @throw std::bad_alloc if unable to copy the PropertyValue
bool AnyVersionHasLabelProperty(Vertex *vertex, LabelId label, PropertyId key,
const PropertyValue &value,
uint64_t timestamp) {
@ -230,6 +231,7 @@ bool CurrentVersionHasLabel(Vertex *vertex, LabelId label,
// Helper function for iterating through label-property index. Returns true if
// this transaction can see the given vertex, and the visible version has the
// given label and property.
// @throw std::bad_alloc if unable to copy the PropertyValue
bool CurrentVersionHasLabelProperty(Vertex *vertex, LabelId label,
PropertyId key, const PropertyValue &value,
Transaction *transaction, View view) {

View File

@ -42,6 +42,7 @@ class LabelIndex {
public:
explicit LabelIndex(Indices *indices) : indices_(indices) {}
/// @throw std::bad_alloc
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
@ -87,6 +88,7 @@ class LabelIndex {
};
/// Returns an self with vertices visible from the given transaction.
/// @throw std::bad_alloc
Iterable Vertices(LabelId label, View view, Transaction *transaction) {
return Iterable(GetOrCreateStorage(label)->access(), label, view,
transaction, indices_);
@ -100,6 +102,7 @@ class LabelIndex {
utils::SkipList<LabelStorage> index_;
Indices *indices_;
/// @throw std::bad_alloc
utils::SkipList<Entry> *GetOrCreateStorage(LabelId label);
};
@ -120,11 +123,14 @@ class LabelPropertyIndex {
public:
explicit LabelPropertyIndex(Indices *indices) : indices_(indices) {}
/// @throw std::bad_alloc
void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
/// @throw std::bad_alloc
void UpdateOnSetProperty(PropertyId property, const PropertyValue &value,
Vertex *vertex, const Transaction &tx);
/// @throw std::bad_alloc
bool CreateIndex(LabelId label, PropertyId property,
utils::SkipList<Vertex>::Accessor vertices);
@ -136,10 +142,12 @@ class LabelPropertyIndex {
return index_.find({label, property}) != index_.end();
}
/// @throw std::bad_alloc if unable to copy a PropertyValue
void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
class Iterable {
public:
/// @throw std::bad_alloc if unable to copy a PropertyValue
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label,
PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
@ -148,6 +156,7 @@ class LabelPropertyIndex {
class Iterator {
public:
/// @throw std::bad_alloc raised in AdvanceUntilValid
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
VertexAccessor operator*() const { return current_vertex_accessor_; }
@ -159,9 +168,11 @@ class LabelPropertyIndex {
return index_iterator_ != other.index_iterator_;
}
/// @throw std::bad_alloc raised in AdvanceUntilValid
Iterator &operator++();
private:
/// @throw std::bad_alloc if unable to copy a PropertyValue
void AdvanceUntilValid();
Iterable *self_;
@ -184,6 +195,7 @@ class LabelPropertyIndex {
Indices *indices_;
};
/// @throw std::bad_alloc if unable to copy a PropertyValue
Iterable Vertices(
LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
@ -234,6 +246,7 @@ struct Indices {
/// This function should be called from garbage collection to clean-up the
/// index.
/// @throw std::bad_alloc raised in LabelPropertyIndex::RemoveObsoleteEntries
void RemoveObsoleteEntries(Indices *indices,
uint64_t oldest_active_start_timestamp);
@ -242,10 +255,12 @@ void RemoveObsoleteEntries(Indices *indices,
// view for use in Merge.
/// This function should be called whenever a label is added to a vertex.
/// @throw std::bad_alloc
void UpdateOnAddLabel(Indices *indices, LabelId label, Vertex *vertex,
const Transaction &tx);
/// This function should be called whenever a property is modified on a vertex.
/// @throw std::bad_alloc
void UpdateOnSetProperty(Indices *indices, PropertyId property,
const PropertyValue &value, Vertex *vertex,
const Transaction &tx);

View File

@ -69,6 +69,7 @@ inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
/// a pointer to the created delta. It doesn't perform any linking of the delta
/// and is primarily used to create the first delta for an object (that must be
/// a `DELETE_OBJECT` delta).
/// @throw std::bad_alloc
inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
transaction->EnsureCommitTimestampExists();
return &transaction->deltas.emplace_back(Delta::DeleteObjectTag(),
@ -78,6 +79,7 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
/// This function creates a delta in the transaction for the object and links
/// the delta into the object's delta list.
/// @throw std::bad_alloc
template <typename TObj, class... Args>
inline void CreateAndLinkDelta(Transaction *transaction, TObj *object,
Args &&... args) {

View File

@ -32,6 +32,7 @@ class NameIdMapper final {
};
public:
/// @throw std::bad_alloc if unable to insert a new mapping
uint64_t NameToId(const std::string &name) {
auto name_to_id_acc = name_to_id_.access();
auto found = name_to_id_acc.find(name);

View File

@ -39,16 +39,22 @@ class PropertyValue {
}
// copy constructors for non-primitive types
/// @throw std::bad_alloc
explicit PropertyValue(const std::string &value) : type_(Type::String) {
new (&string_v) std::string(value);
}
/// @throw std::bad_alloc
/// @throw std::length_error if length of value exceeds
/// std::string::max_length().
explicit PropertyValue(const char *value) : type_(Type::String) {
new (&string_v) std::string(value);
}
/// @throw std::bad_alloc
explicit PropertyValue(const std::vector<PropertyValue> &value)
: type_(Type::List) {
new (&list_v) std::vector<PropertyValue>(value);
}
/// @throw std::bad_alloc
explicit PropertyValue(const std::map<std::string, PropertyValue> &value)
: type_(Type::Map) {
new (&map_v) std::map<std::string, PropertyValue>(value);
@ -68,12 +74,14 @@ class PropertyValue {
}
// copy constructor
/// @throw std::bad_alloc
PropertyValue(const PropertyValue &other);
// move constructor
PropertyValue(PropertyValue &&other) noexcept;
// copy assignment
/// @throw std::bad_alloc
PropertyValue &operator=(const PropertyValue &other);
// move assignment
@ -95,18 +103,21 @@ class PropertyValue {
bool IsMap() const { return type_ == Type::Map; }
// value getters for primitive types
/// @throw PropertyValueException if value isn't of correct type.
bool ValueBool() const {
if (type_ != Type::Bool) {
throw PropertyValueException("The value isn't a bool!");
}
return bool_v;
}
/// @throw PropertyValueException if value isn't of correct type.
int64_t ValueInt() const {
if (type_ != Type::Int) {
throw PropertyValueException("The value isn't an int!");
}
return int_v;
}
/// @throw PropertyValueException if value isn't of correct type.
double ValueDouble() const {
if (type_ != Type::Double) {
throw PropertyValueException("The value isn't a double!");
@ -115,6 +126,7 @@ class PropertyValue {
}
// const value getters for non-primitive types
/// @throw PropertyValueException if value isn't of correct type.
const std::string &ValueString() const {
if (type_ != Type::String) {
throw PropertyValueException("The value isn't a string!");
@ -122,6 +134,7 @@ class PropertyValue {
return string_v;
}
/// @throw PropertyValueException if value isn't of correct type.
const std::vector<PropertyValue> &ValueList() const {
if (type_ != Type::List) {
throw PropertyValueException("The value isn't a list!");
@ -129,6 +142,7 @@ class PropertyValue {
return list_v;
}
/// @throw PropertyValueException if value isn't of correct type.
const std::map<std::string, PropertyValue> &ValueMap() const {
if (type_ != Type::Map) {
throw PropertyValueException("The value isn't a map!");
@ -137,6 +151,7 @@ class PropertyValue {
}
// reference value getters for non-primitive types
/// @throw PropertyValueException if value isn't of correct type.
std::string &ValueString() {
if (type_ != Type::String) {
throw PropertyValueException("The value isn't a string!");
@ -144,6 +159,7 @@ class PropertyValue {
return string_v;
}
/// @throw PropertyValueException if value isn't of correct type.
std::vector<PropertyValue> &ValueList() {
if (type_ != Type::List) {
throw PropertyValueException("The value isn't a list!");
@ -151,6 +167,7 @@ class PropertyValue {
return list_v;
}
/// @throw PropertyValueException if value isn't of correct type.
std::map<std::string, PropertyValue> &ValueMap() {
if (type_ != Type::Map) {
throw PropertyValueException("The value isn't a map!");
@ -174,6 +191,7 @@ class PropertyValue {
};
// stream output
/// @throw anything std::ostream::operator<< may throw.
inline std::ostream &operator<<(std::ostream &os,
const PropertyValue::Type type) {
switch (type) {
@ -193,6 +211,7 @@ inline std::ostream &operator<<(std::ostream &os,
return os << "map";
}
}
/// @throw anything std::ostream::operator<< may throw.
inline std::ostream &operator<<(std::ostream &os, const PropertyValue &value) {
switch (value.type()) {
case PropertyValue::Type::Null:
@ -221,7 +240,7 @@ inline std::ostream &operator<<(std::ostream &os, const PropertyValue &value) {
// comparison
inline bool operator==(const PropertyValue &first,
const PropertyValue &second) {
const PropertyValue &second) noexcept {
if (first.type() != second.type()) return false;
switch (first.type()) {
case PropertyValue::Type::Null:
@ -240,7 +259,9 @@ inline bool operator==(const PropertyValue &first,
return first.ValueMap() == second.ValueMap();
}
}
inline bool operator<(const PropertyValue &first, const PropertyValue &second) {
inline bool operator<(const PropertyValue &first,
const PropertyValue &second) noexcept {
if (first.type() != second.type()) return first.type() < second.type();
switch (first.type()) {
case PropertyValue::Type::Null:

View File

@ -140,6 +140,8 @@ class VerticesIterable final {
VertexAccessor operator*() const;
/// @throw std::bad_alloc raised in
/// LabelPropertyIndex::Iterable::Iterator::operator++
Iterator &operator++();
bool operator==(const Iterator &other) const;
@ -152,6 +154,8 @@ class VerticesIterable final {
class Storage final {
public:
/// @throw std::system_error
/// @throw std::bad_alloc
explicit Storage(StorageGcConfig gc_config = DefaultGcConfig);
~Storage();
@ -174,6 +178,7 @@ class Storage final {
~Accessor();
/// @throw std::bad_alloc
VertexAccessor CreateVertex();
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
@ -184,13 +189,17 @@ class Storage final {
&storage_->indices_));
}
/// @throw std::bad_alloc raised in Index::Vertices
VerticesIterable Vertices(LabelId label, View view);
/// @throw std::bad_alloc raised in Index::Vertices
VerticesIterable Vertices(LabelId label, PropertyId property, View view);
/// @throw std::bad_alloc raised in Index::Vertices
VerticesIterable Vertices(LabelId label, PropertyId property,
const PropertyValue &value, View view);
/// @throw std::bad_alloc raised in Index::Vertices
VerticesIterable Vertices(
LabelId label, PropertyId property,
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
@ -236,21 +245,30 @@ class Storage final {
label, property, lower, upper);
}
/// @throw std::bad_alloc
Result<bool> DeleteVertex(VertexAccessor *vertex);
/// @throw std::bad_alloc
Result<bool> DetachDeleteVertex(VertexAccessor *vertex);
/// @throw std::bad_alloc
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to,
EdgeTypeId edge_type);
/// @throw std::bad_alloc
Result<bool> DeleteEdge(EdgeAccessor *edge);
const std::string &LabelToName(LabelId label) const;
const std::string &PropertyToName(PropertyId property) const;
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
/// @throw std::bad_alloc if unable to insert a new mapping
LabelId NameToLabel(const std::string &name);
/// @throw std::bad_alloc if unable to insert a new mapping
PropertyId NameToProperty(const std::string &name);
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(const std::string &name);
void AdvanceCommand();
@ -258,8 +276,10 @@ class Storage final {
/// Commit returns `ExistenceConstraintViolation` if the changes made by
/// this transaction violate an existence constraint. In that case the
/// transaction is automatically aborted. Otherwise, nullopt is returned.
/// @throw std::bad_alloc
[[nodiscard]] std::optional<ExistenceConstraintViolation> Commit();
/// @throw std::bad_alloc
void Abort();
private:
@ -276,10 +296,16 @@ class Storage final {
const std::string &PropertyToName(PropertyId property) const;
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
/// @throw std::bad_alloc if unable to insert a new mapping
LabelId NameToLabel(const std::string &name);
/// @throw std::bad_alloc if unable to insert a new mapping
PropertyId NameToProperty(const std::string &name);
/// @throw std::bad_alloc if unable to insert a new mapping
EdgeTypeId NameToEdgeType(const std::string &name);
/// @throw std::bad_alloc
bool CreateIndex(LabelId label, PropertyId property) {
std::unique_lock<utils::RWLock> storage_guard(main_lock_);
return indices_.label_property_index.CreateIndex(label, property,
@ -317,6 +343,8 @@ class Storage final {
}
private:
/// @throw std::system_error
/// @throw std::bad_alloc
void CollectGarbage();
// Main storage lock.

View File

@ -36,6 +36,7 @@ struct Transaction {
~Transaction() {}
/// @throw std::bad_alloc if failed to create the `commit_timestamp`
void EnsureCommitTimestampExists() {
if (commit_timestamp != nullptr) return;
commit_timestamp = std::make_unique<std::atomic<uint64_t>>(transaction_id);

View File

@ -26,12 +26,21 @@ class VertexAccessor final {
Transaction *transaction,
Indices *indices, View view);
/// Add a label and return `true` if insertion took place.
/// `false` is returned if the label already existed.
/// @throw std::bad_alloc
Result<bool> AddLabel(LabelId label);
/// Remove a label and return `true` if deletion took place.
/// `false` is returned if the vertex did not have a label already.
/// @throw std::bad_alloc
Result<bool> RemoveLabel(LabelId label);
Result<bool> HasLabel(LabelId label, View view) const;
/// @throw std::bad_alloc
/// @throw std::length_error if the resulting vector exceeds
/// std::vector::max_size().
Result<std::vector<LabelId>> Labels(View view) const;
/// Set a property value and return `true` if insertion took place.
@ -39,13 +48,22 @@ class VertexAccessor final {
/// @throw std::bad_alloc
Result<bool> SetProperty(PropertyId property, const PropertyValue &value);
/// @throw std::bad_alloc
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
/// @throw std::bad_alloc
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
// TODO: Add API for obtaining edges filtered by destination VertexAccessor
/// @throw std::bad_alloc
/// @throw std::length_error if the resulting vector exceeds
/// std::vector::max_size().
Result<std::vector<EdgeAccessor>> InEdges(
const std::vector<EdgeTypeId> &edge_types, View view) const;
/// @throw std::bad_alloc
/// @throw std::length_error if the resulting vector exceeds
/// std::vector::max_size().
Result<std::vector<EdgeAccessor>> OutEdges(
const std::vector<EdgeTypeId> &edge_types, View view) const;
@ -73,7 +91,7 @@ class VertexAccessor final {
namespace std {
template <>
struct hash<storage::VertexAccessor> {
size_t operator()(const storage::VertexAccessor &v) const {
size_t operator()(const storage::VertexAccessor &v) const noexcept {
return v.Gid().AsUint();
}
};

View File

@ -26,6 +26,8 @@ class Scheduler {
* @param f - Function
* @Tparam TRep underlying arithmetic type in duration
* @Tparam TPeriod duration in seconds between two ticks
* @throw std::system_error if thread could not be started.
* @throw std::bad_alloc
*/
template <typename TRep, typename TPeriod>
void Run(const std::string &service_name,
@ -67,6 +69,7 @@ class Scheduler {
/**
* @brief Stops the thread execution. This is a blocking call and may take as
* much time as one call to the function given previously to Run takes.
* @throw std::system_error
*/
void Stop() {
is_working_.store(false);