Files
qbit-cluster/src/main/java/com/kingsrook/qbits/cluster/runtime/ClusterSystem.java
T
james.maesandClaude Opus 5.5 00bc20b899 feat: qbit-cluster v1 - node registry, heartbeats, leases, drain, lost-node alerts
- cluster_node, cluster_lease, cluster_setting as standard QQQ tables
  (cluster.read / cluster.write), node status possible values, help for
  every table, field, process, widget and value source
- Liquibase changelogs in the jar: schema (idempotent, no FKs, no
  superuser) and insert-only seeded settings
- runtime: register + heartbeat on the database clock, drain requests
  flowing to QNodeLifecycle.startDraining, DRAINING/STOPPED tracking,
  lost-node sweep on the cluster-sweeper lease with the cluster.node.lost
  alert, idempotent purge of old rows; ClusterRuntimeService
- ClusterLeases: one atomic compare-and-set UPDATE per change
  (ConditionalUpdateAction), tryAcquire / renew / release / isLeader
- record actions Drain node and Cancel drain (standard ETL Review /
  Result, cluster.drain), Cluster app with the Nodes widget by role,
  cluster.node.heartbeat health check
- CI (CircleCI qqq-orb), release-rc workflow, docs, 35 tests (memory
  backend, plus Liquibase and the RDBMS module on H2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:13:14 -05:00

101 lines
3.4 KiB
Java

/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* 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
*
* https://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.
*/
package com.kingsrook.qbits.cluster.runtime;
import com.kingsrook.qqq.backend.core.context.CapturedContext;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
/*******************************************************************************
** Runs the qbit's own bookkeeping on the cluster_ tables as the system user
** (never a person's work), then restores the caller's context.
*******************************************************************************/
public final class ClusterSystem
{
/*******************************************************************************
** A body that returns a value and may throw.
*******************************************************************************/
@FunctionalInterface
public interface Body<T>
{
/*******************************************************************************
** Run the body.
*******************************************************************************/
T run() throws QException;
}
/*******************************************************************************
** Utility class.
*******************************************************************************/
private ClusterSystem()
{
}
/*******************************************************************************
** Run in the current instance as the system user.
*******************************************************************************/
public static <T> T call(Body<T> body) throws QException
{
QInstance qInstance = QContext.getQInstance();
if(qInstance == null)
{
throw (new QException("The Cluster qbit needs a QContext with an instance"));
}
return (call(qInstance, body));
}
/*******************************************************************************
** Run in the given instance as the system user (no transaction).
*******************************************************************************/
public static <T> T call(QInstance qInstance, Body<T> body) throws QException
{
CapturedContext original = QContext.capture();
try
{
QContext.init(qInstance, new QSystemUserSession(qInstance));
return (body.run());
}
finally
{
if(original.qInstance() == null)
{
QContext.clear();
}
else
{
QContext.init(original);
}
}
}
}