Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/nine-client-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ jobs:
shell: bash
run: cd clients/python && PYTHONPATH=src python3 -m live_mutex_client.smoke

- name: Shell syntax checks
shell: bash
run: bash -n ./clients/shell/live_mutex_client.sh ./clients/shell/smoke.sh

- name: Shell source contract
shell: bash
run: bash ./clients/shell/source-contract.sh
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ores-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Detect project types
id: detect
Expand All @@ -26,7 +26,7 @@ jobs:

# Node is the runner for ESLint and for the cross-language require-send
# scanner, so it is installed whenever any supported language is present.
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
if: steps.detect.outputs.js == 'true' || steps.detect.outputs.rs == 'true' || steps.detect.outputs.dart == 'true' || steps.detect.outputs.gleam == 'true'
with:
node-version: '22'
Expand All @@ -43,7 +43,7 @@ jobs:

# Dart/Gleam SDKs are optional. When absent, dart.sh / gleam.sh skip
# with an actionable message; require-send.mjs still runs via Node.
- uses: dart-lang/setup-dart@v1
- uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1
if: steps.detect.outputs.dart == 'true'
continue-on-error: true

Expand Down
90 changes: 66 additions & 24 deletions clients/cpp/include/live_mutex/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,18 @@ class Value {
private:
void write(std::ostringstream& os) const {
switch (type_) {
case Type::Null: os << "null"; break;
case Type::Bool: os << (bool_ ? "true" : "false"); break;
case Type::Number: os << str_; break;
case Type::String: write_string(os, str_); break;
case Type::Null:
os << "null";
break;
case Type::Bool:
os << (bool_ ? "true" : "false");
break;
case Type::Number:
os << str_;
break;
case Type::String:
write_string(os, str_);
break;
case Type::Array: {
os << '[';
for (size_t i = 0; i < arr_.size(); ++i) {
Expand Down Expand Up @@ -125,11 +133,21 @@ class Value {
os << '"';
for (char c : s) {
switch (c) {
case '"': os << "\\\""; break;
case '\\': os << "\\\\"; break;
case '\n': os << "\\n"; break;
case '\r': os << "\\r"; break;
case '\t': os << "\\t"; break;
case '"':
os << "\\\"";
break;
case '\\':
os << "\\\\";
break;
case '\n':
os << "\\n";
break;
case '\r':
os << "\\r";
break;
case '\t':
os << "\\t";
break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char buf[8];
Expand Down Expand Up @@ -186,12 +204,19 @@ class Parser {
Value parse_value() {
char c = peek();
switch (c) {
case '{': return parse_object();
case '[': return parse_array();
case '"': return Value(parse_string());
case 't': case 'f': return parse_bool();
case 'n': return parse_null();
default: return parse_number();
case '{':
return parse_object();
case '[':
return parse_array();
case '"':
return Value(parse_string());
case 't':
case 'f':
return parse_bool();
case 'n':
return parse_null();
default:
return parse_number();
}
}

Expand Down Expand Up @@ -271,14 +296,30 @@ class Parser {
}
char e = s_[i_++];
switch (e) {
case '"': out.push_back('"'); break;
case '\\': out.push_back('\\'); break;
case '/': out.push_back('/'); break;
case 'n': out.push_back('\n'); break;
case 'r': out.push_back('\r'); break;
case 't': out.push_back('\t'); break;
case 'b': out.push_back('\b'); break;
case 'f': out.push_back('\f'); break;
case '"':
out.push_back('"');
break;
case '\\':
out.push_back('\\');
break;
case '/':
out.push_back('/');
break;
case 'n':
out.push_back('\n');
break;
case 'r':
out.push_back('\r');
break;
case 't':
out.push_back('\t');
break;
case 'b':
out.push_back('\b');
break;
case 'f':
out.push_back('\f');
break;
case 'u': {
if (i_ + 4 > s_.size()) {
throw ParseError("bad \\u escape");
Expand All @@ -297,7 +338,8 @@ class Parser {
}
break;
}
default: throw ParseError("unknown escape");
default:
throw ParseError("unknown escape");
}
} else {
out.push_back(c);
Expand Down
54 changes: 39 additions & 15 deletions clients/cpp/include/live_mutex/protocol.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,34 @@ struct Response {
r.contended_key = r.raw.str_or("contendedKey");

if (const json::Value* ks = r.raw.find("keys"); ks && ks->type() == json::Type::Array) {
for (const auto& v : ks->as_array())
if (v.type() == json::Type::String) r.keys.push_back(v.as_string());
for (const auto& v : ks->as_array()) {
if (v.type() == json::Type::String) {
r.keys.push_back(v.as_string());
}
}
}
if (const json::Value* ft = r.raw.find("fencingTokens"); ft && ft->type() == json::Type::Object) {
for (const auto& [k, v] : ft->as_object())
if (v.type() == json::Type::Number) r.fencing_tokens[k] = v.as_u64();
for (const auto& [k, v] : ft->as_object()) {
if (v.type() == json::Type::Number) {
r.fencing_tokens[k] = v.as_u64();
}
}
}

if (r.raw_type == "lock") r.type = ResponseType::Lock;
else if (r.raw_type == "unlock" || r.raw.contains("unlocked")) r.type = ResponseType::Unlock;
else if (r.raw_type == "version-mismatch") r.type = ResponseType::VersionMismatch;
else if (r.raw.contains("released")) r.type = ResponseType::ReleaseMany;
else if (r.raw.contains("fencingTokens") || (r.raw.contains("lockUuid") && r.raw.contains("acquired")))
if (r.raw_type == "lock") {
r.type = ResponseType::Lock;
} else if (r.raw_type == "unlock" || r.raw.contains("unlocked")) {
r.type = ResponseType::Unlock;
} else if (r.raw_type == "version-mismatch") {
r.type = ResponseType::VersionMismatch;
} else if (r.raw.contains("released")) {
r.type = ResponseType::ReleaseMany;
} else if (
r.raw.contains("fencingTokens") ||
(r.raw.contains("lockUuid") && r.raw.contains("acquired"))
) {
r.type = ResponseType::AcquireMany;
}
return r;
}
};
Expand All @@ -85,9 +99,14 @@ inline std::string lock_request(const std::string& uuid, const std::string& key,
o["key"] = key;
o["pid"] = static_cast<int64_t>(pid);
o["keepLocksAfterDeath"] = false;
if (ttl_ms > 0) o["ttl"] = static_cast<uint64_t>(ttl_ms);
else o["ttl"] = nullptr;
if (max_holders) o["max"] = static_cast<uint64_t>(*max_holders);
if (ttl_ms > 0) {
o["ttl"] = static_cast<uint64_t>(ttl_ms);
} else {
o["ttl"] = nullptr;
}
if (max_holders) {
o["max"] = static_cast<uint64_t>(*max_holders);
}
return json::Value(std::move(o)).dump();
}

Expand All @@ -105,13 +124,18 @@ inline std::string unlock_request(const std::string& uuid, const std::string& ke
inline std::string acquire_many_request(const std::string& uuid,
const std::vector<std::string>& keys, uint64_t ttl_ms) {
json::Array arr;
for (const auto& k : keys) arr.push_back(json::Value(k));
for (const auto& k : keys) {
arr.push_back(json::Value(k));
}
json::Object o;
o["type"] = std::string("acquire-many");
o["uuid"] = uuid;
o["keys"] = json::Value(std::move(arr));
if (ttl_ms > 0) o["ttl"] = static_cast<uint64_t>(ttl_ms);
else o["ttl"] = nullptr;
if (ttl_ms > 0) {
o["ttl"] = static_cast<uint64_t>(ttl_ms);
} else {
o["ttl"] = nullptr;
}
return json::Value(std::move(o)).dump();
}

Expand Down
27 changes: 23 additions & 4 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,27 @@
// not overwrite this file once you have changed it.
import oresConfig from './.ores-lint/eslint/base.mjs';

export default await oresConfig({
// requireSend: { loggerNames: ['myLogger'], terminalMethods: ['send', 'flush'] },
// rules: { 'no-console': 'warn' },
// ignores: ['**/generated/**'],
const baseConfig = await oresConfig({
rules: {
curly: ['warn', 'all'],
},
});

export default [
...baseConfig,
{
files: [
'src/**/*.ts',
'test/**/*.ts',
'formal/**/*.mjs',
'scripts/**/*.js',
'clients/**/*.js',
'clients/**/*.mjs',
'clients/**/*.ts',
'clients/**/*.tsx',
],
rules: {
'max-statements-per-line': ['warn', {max: 1}],
},
},
];
Loading