Arachne 1.0
Arachne - the perpetual stitcher of Wikidata entities.
Loading...
Searching...
No Matches
arachne::coordination::domain_lock Class Reference

#include <include/arachne/coordinator.hpp>

Public Member Functions

 domain_lock (const std::filesystem::path &lock_root, std::string_view graph_domain, std::string_view run_id, std::chrono::seconds stale_after=std::chrono::hours(6))
 ~domain_lock ()
 domain_lock (const domain_lock &)=delete
domain_lockoperator= (const domain_lock &)=delete
 domain_lock (domain_lock &&other) noexcept
domain_lockoperator= (domain_lock &&other) noexcept
bool owns_lock () const noexcept
void release ()

Private Attributes

std::filesystem::path directory_
std::string ownership_token_
bool owns_ = false

Detailed Description

Definition at line 155 of file coordinator.hpp.

Constructor & Destructor Documentation

◆ domain_lock() [1/3]

arachne::coordination::domain_lock::domain_lock ( const std::filesystem::path & lock_root,
std::string_view graph_domain,
std::string_view run_id,
std::chrono::seconds stale_after = std::chrono::hours(6) )

Definition at line 1569 of file coordinator.cpp.

1570 {
1571 throw std::logic_error(
1572 "running product attempt provenance is missing"
1573 );
1574 }
1575 execute(database, "COMMIT;");
1576 } catch (...) {
1577 execute(database, "ROLLBACK;");
1578 throw;
1579 }
1580}
1581
1583 const std::string_view run_id, const std::string_view status,
1584 const std::string_view manifest_ref
1585) {
1586 if (status != "succeeded" && status != "failed") {
1587 throw std::invalid_argument("run status must be succeeded or failed");
1588 }
1589 if (!is_safe_identifier(run_id)) {
1590 throw std::invalid_argument("run_id must be a safe stable identifier");
1591 }
1592 sqlite3* database = impl_->database.get();
1593 execute(database, "BEGIN IMMEDIATE;");
1594 try {
1595 int attempt_number = 0;
1596 {
1597 auto current = prepare(database, R"SQL(
1598SELECT attempt_count FROM runs WHERE run_id=? AND status='running'
1599)SQL");
1600 bind_text(database, current.get(), 1, run_id);
1601 const int result = sqlite3_step(current.get());
1602 if (result != SQLITE_ROW) {
1603 if (result != SQLITE_DONE) {
1604 throw_sqlite(database, "inspect running run");
1605 }
1606 throw std::logic_error("run is unknown or already finished");
1607 }
1608 attempt_number = sqlite3_column_int(current.get(), 0);
1609 }
1610 const std::string finished_at = utc_timestamp();
1611 auto statement = prepare(database, R"SQL(
1612UPDATE runs SET status = ?, manifest_ref = ?, finished_at = ?
1613WHERE run_id = ? AND status = 'running'
1614)SQL");
1615 bind_text(database, statement.get(), 1, status);
1616 bind_text(database, statement.get(), 2, manifest_ref);
1617 bind_text(database, statement.get(), 3, finished_at);
1618 bind_text(database, statement.get(), 4, run_id);
1619 step_done(database, statement.get());
1620 auto attempt = prepare(database, R"SQL(
1621UPDATE run_attempts SET status=?, manifest_ref=?, finished_at=?
1622WHERE run_id=? AND attempt=? AND status='running'
1623)SQL");
1624 bind_text(database, attempt.get(), 1, status);
1625 bind_text(database, attempt.get(), 2, manifest_ref);
1626 bind_text(database, attempt.get(), 3, finished_at);
1627 bind_text(database, attempt.get(), 4, run_id);
1628 if (sqlite3_bind_int(attempt.get(), 5, attempt_number) != SQLITE_OK) {
1629 throw_sqlite(database, "bind run attempt number");
1630 }
1631 step_done(database, attempt.get());
1632 if (sqlite3_changes(database) != 1) {
1633 throw std::logic_error("running attempt provenance is missing");
1634 }
1635 execute(database, "COMMIT;");
1636 } catch (...) {
1637 execute(database, "ROLLBACK;");
1638 throw;
1639 }
1640}
1641
1642const std::filesystem::path& operational_ledger::path() const noexcept {
1643 return path_;
1644}
1645
1646bool path_is_within(
1647 const std::filesystem::path& path,
1648 const std::filesystem::path& possible_parent
1649) {
1650 const auto child = weakly_canonical_or_absolute(path);
1651 const auto parent = weakly_canonical_or_absolute(possible_parent);
1652 auto child_it = child.begin();
1653 for (auto parent_it = parent.begin(); parent_it != parent.end();
1654 ++parent_it, ++child_it) {
1655 if (child_it == child.end() || *child_it != *parent_it) {
1656 return false;
1657 }
1658 }
1659 return true;
1660}
1661
1663 const std::filesystem::path& target, const std::filesystem::path& inbox_root
1664) {
1665 if (path_is_within(target, inbox_root)
1666 || path_is_within(inbox_root, target)) {
1667 throw std::invalid_argument(
1668 "refusing deletion inside or above the external legacy inbox"
1669 );
1670 }
1671}
1672
1674 const std::filesystem::path& lock_root, const std::string_view graph_domain,
1675 const std::string_view run_id, const std::chrono::seconds stale_after
1676) {
1677 if (graph_domain != "product_graph"
1678 && graph_domain != "research_candidate_graph") {
1679 throw std::invalid_argument("unknown graph domain");
1680 }
1681 if (!is_safe_identifier(run_id)) {
1682 throw std::invalid_argument("run_id must be a safe stable identifier");
1683 }
1684 if (stale_after.count() <= 0) {
1685 throw std::invalid_argument("lock stale interval must be positive");
1686 }
1687 reject_symlink_components(lock_root, "lock root");
1688 std::filesystem::create_directories(lock_root);
1689 reject_symlink_components(lock_root, "lock root");
1690 const auto root = weakly_canonical_or_absolute(lock_root);
1691 if (root == root.root_path()) {
1692 throw std::invalid_argument("lock root must be a scoped directory");
1693 }
1694 directory_ = root / (std::string(graph_domain) + ".lock");
1695 std::error_code error;
1696 const bool created = std::filesystem::create_directory(directory_, error);
1697 if (!created) {
1698 if (error) {
1699 throw std::runtime_error(
1700 "cannot create graph-domain lock: " + error.message()
1701 );
1702 }
1703 const auto lock_status
1704 = std::filesystem::symlink_status(directory_, error);
domain_lock(const std::filesystem::path &lock_root, std::string_view graph_domain, std::string_view run_id, std::chrono::seconds stale_after=std::chrono::hours(6))
void finish_run(std::string_view run_id, std::string_view status, std::string_view manifest_ref={})
const std::filesystem::path & path() const noexcept
void step_done(sqlite3 *database, sqlite3_stmt *statement)
void throw_sqlite(sqlite3 *database, std::string_view context)
void reject_symlink_components(const std::filesystem::path &path, std::string_view context)
std::filesystem::path weakly_canonical_or_absolute(const std::filesystem::path &path)
void execute(sqlite3 *database, std::string_view sql)
statement_handle prepare(sqlite3 *database, std::string_view sql)
void bind_text(sqlite3 *database, sqlite3_stmt *statement, int index, std::string_view value)
bool path_is_within(const std::filesystem::path &path, const std::filesystem::path &possible_parent)
void reject_inbox_deletion_target(const std::filesystem::path &target, const std::filesystem::path &inbox_root)

◆ ~domain_lock()

arachne::coordination::domain_lock::~domain_lock ( )

Definition at line 1706 of file coordinator.cpp.

1706 {
1707 throw std::runtime_error(
1708 "graph-domain lock path is not a safe directory"
1709 );
1710 }

◆ domain_lock() [2/3]

arachne::coordination::domain_lock::domain_lock ( const domain_lock & )
delete

◆ domain_lock() [3/3]

arachne::coordination::domain_lock::domain_lock ( domain_lock && other)
noexcept

Definition at line 1712 of file coordinator.cpp.

1714 {
1715 throw std::runtime_error(
1716 "graph-domain lock lease is missing or unsafe; maintainer "
1717 "recovery required"
1718 );

Member Function Documentation

◆ operator=() [1/2]

domain_lock & arachne::coordination::domain_lock::operator= ( const domain_lock & )
delete

◆ operator=() [2/2]

domain_lock & arachne::coordination::domain_lock::operator= ( domain_lock && other)
noexcept

Definition at line 1720 of file coordinator.cpp.

1720 {
1721 std::ifstream input(lease, std::ios::binary);
1722 nlohmann::json document;
1723 if (!input) {
1724 throw std::runtime_error("cannot read graph-domain lock lease");
1725 }
1726 input >> document;
1727 if (!document.is_object()
1728 || document.value("format_version", 0) != 1
1729 || document.value("graph_domain", std::string {})
1730 != graph_domain
1731 || !is_safe_identifier(document.value("run_id", std::string {}))
1732 || !document.contains("acquired_unix")

◆ owns_lock()

bool arachne::coordination::domain_lock::owns_lock ( ) const
nodiscardnoexcept

Definition at line 1734 of file coordinator.cpp.

◆ release()

void arachne::coordination::domain_lock::release ( )

Definition at line 1736 of file coordinator.cpp.

1741 {
1742 throw std::runtime_error(
1743 "graph-domain lock lease ownership token is malformed"
1744 );
1745 }
1746 const auto acquired
1747 = document.at("acquired_unix").get<std::int64_t>();
1748 if (acquired <= 0) {
1749 throw std::runtime_error(
1750 "graph-domain lock lease timestamp is malformed"
1751 );
1752 }
1753 if (unix_seconds() - acquired >= stale_after.count()) {
1754 throw std::runtime_error(
1755 "graph-domain lock is stale; maintainer recovery required"
1756 );
1757 }
1758 } catch (const nlohmann::json::exception&) {
1759 throw std::runtime_error("graph-domain lock lease is malformed");
1760 }
1761 throw std::runtime_error("graph domain already has an active writer");
1762 }
1763 std::random_device entropy;
1765 std::string(run_id) + "\n" + std::string(graph_domain) + "\n"
1766 + std::to_string(
1767 std::chrono::high_resolution_clock::now().time_since_epoch().count()
1768 )
1769 + "\n" + std::to_string(lock_token_sequence.fetch_add(1)) + "\n"
1770 + std::to_string(entropy())
1771 );
1772 const nlohmann::ordered_json lease {
1773 { "format_version", 1 },
1774 { "run_id", run_id },
1775 { "graph_domain", graph_domain },
1776 { "acquired_unix", unix_seconds() },
1777 { "ownership_token", ownership_token_ },
1778 };
1779 const auto lease_path = directory_ / "lease.json";
1780 const auto temporary
1781 = directory_ / (".lease-" + ownership_token_.substr(0, 16) + ".tmp");
1782 try {
1783 std::ofstream output(temporary, std::ios::binary | std::ios::trunc);
1784 if (!output) {
1785 throw std::runtime_error("cannot write graph-domain lease");
1786 }
1787 output << lease.dump(2) << '\n';
1788 output.flush();
1789 output.close();
1790 if (!output) {
1791 throw std::runtime_error("cannot flush graph-domain lease");
1792 }
1793 std::filesystem::rename(temporary, lease_path, error);
1794 if (error) {
1795 throw std::runtime_error(
1796 "cannot publish graph-domain lease: " + error.message()
1797 );
1798 }
1799 } catch (...) {
1800 std::error_code ignored;
1801 std::filesystem::remove(temporary, ignored);
1802 std::filesystem::remove(lease_path, ignored);
1803 std::filesystem::remove(directory_, ignored);
std::string sha256(std::span< const std::byte > bytes)
Definition crypto.cpp:209

Member Data Documentation

◆ directory_

std::filesystem::path arachne::coordination::domain_lock::directory_
private

Definition at line 173 of file coordinator.hpp.

◆ ownership_token_

std::string arachne::coordination::domain_lock::ownership_token_
private

Definition at line 174 of file coordinator.hpp.

◆ owns_

bool arachne::coordination::domain_lock::owns_ = false
private

Definition at line 175 of file coordinator.hpp.


The documentation for this class was generated from the following files: