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: 3 additions & 1 deletion src/commands/watch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ export async function runWatch(
}
}

const handle = startWatchdog(config, registry);
const inFlight = new Set<string>();
const handle = startWatchdog(config, registry, inFlight);
const scheduler = startScheduler(
config,
registry,
handle.emitter,
handle.states,
inFlight,
);

if (opts.daemon) {
Expand Down
10 changes: 8 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ program
);
const response = await new Promise<string>((resolve) => {
process.stdin.setEncoding("utf8");
process.stdin.once("data", (data) => resolve(data.toString().trim()));
process.stdin.once("data", (data) => {
process.stdin.pause();
resolve(data.toString().trim());
});
process.stdin.resume();
});
if (response.toLowerCase() !== "y") {
Expand Down Expand Up @@ -146,7 +149,10 @@ program
);
const response = await new Promise<string>((resolve) => {
process.stdin.setEncoding("utf8");
process.stdin.once("data", (data) => resolve(data.toString().trim()));
process.stdin.once("data", (data) => {
process.stdin.pause();
resolve(data.toString().trim());
});
process.stdin.resume();
});
if (response.toLowerCase() !== "y") {
Expand Down
28 changes: 27 additions & 1 deletion src/watchdog/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface WatchdogHandle {
export function startWatchdog(
config: LobsterdConfig,
registry: TenantRegistry,
inFlight: Set<string>,
): WatchdogHandle {
const emitter = new WatchdogEmitter();
const tenantStates: Record<string, TenantWatchState> = {};
Expand All @@ -41,21 +42,46 @@ export function startWatchdog(
const freshResult = await loadRegistry();
if (freshResult.isOk()) {
const fresh = freshResult.value;

// Update existing tenants from disk
for (const freshTenant of fresh.tenants) {
if (inFlight.has(freshTenant.name)) {
continue;
}
const idx = registry.tenants.findIndex(
(t) => t.name === freshTenant.name,
);
if (idx !== -1) {
Object.assign(registry.tenants[idx], freshTenant);
}
}

// Add newly spawned tenants
for (const freshTenant of fresh.tenants) {
if (!registry.tenants.some((t) => t.name === freshTenant.name)) {
registry.tenants.push(freshTenant);
tenantStates[freshTenant.name] = initialWatchState();
}
}

// Remove evicted tenants (skip in-flight)
for (let i = registry.tenants.length - 1; i >= 0; i--) {
const name = registry.tenants[i].name;
if (inFlight.has(name)) {
continue;
}
if (!fresh.tenants.some((t) => t.name === name)) {
registry.tenants.splice(i, 1);
delete tenantStates[name];
}
}
}

for (const tenant of registry.tenants) {
if (!running) {
break;
}
if (tenant.status === "removing") {
if (tenant.status === "removing" || inFlight.has(tenant.name)) {
continue;
}
if (tenant.status === "suspended") {
Expand Down
3 changes: 1 addition & 2 deletions src/watchdog/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export function startScheduler(
registry: TenantRegistry,
emitter: WatchdogEmitter,
getStates: () => Record<string, TenantWatchState>,
inFlight: Set<string>,
): SchedulerHandle {
let running = true;

Expand All @@ -87,8 +88,6 @@ export function startScheduler(
const cronTimers = new Map<string, ReturnType<typeof setTimeout>>();
// Track sentinel listeners for suspended tenants
const sentinels = new Map<string, SentinelHandle>();
// Track in-flight operations to prevent races
const inFlight = new Set<string>();

function clearCronTimer(name: string) {
const timer = cronTimers.get(name);
Expand Down
22 changes: 1 addition & 21 deletions web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ function Hero() {
<section ref={ref} className="min-h-screen pt-20 border-b border-[#D4D4D8]">
<div className="mx-auto max-w-[1600px] px-8 grid grid-cols-1 lg:grid-cols-12 gap-8 items-center min-h-[calc(100vh-80px)]">
{/* Left side */}
<div className="lg:col-span-7 py-16 lg:py-24">
<div className="lg:col-span-7 py-16 lg:py-24 relative z-10">
{/* Status pill */}
<div
className="inline-flex items-center gap-2.5 mb-12"
Expand Down Expand Up @@ -618,26 +618,6 @@ function FeatureRow({ feature, index }: { feature: Feature; index: number }) {
{feature.desc}
</p>
</div>
{/* View circle */}
<div
className="w-14 h-14 rounded-full border border-dark flex items-center justify-center shrink-0 hidden lg:flex"
style={{
opacity: hovered ? 1 : 0,
transform: hovered ? "scale(1)" : "scale(0.5)",
transition: `all 0.5s ${EXPO}`,
}}
>
<svg
aria-hidden="true"
className="w-3.5 h-3.5"
viewBox="0 0 16 16"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<path d="M1 15L15 1M15 1H5M15 1V11" />
</svg>
</div>
</div>
</div>
</div>
Expand Down