Skip to content

Commit

Permalink
fix: debounce kubernetes listeners channels individually (podman-desk…
Browse files Browse the repository at this point in the history
…top#6280)

* fix: debounce channels individually
Signed-off-by: Philippe Martin <[email protected]>

* refactor: dispatch
Signed-off-by: Philippe Martin <[email protected]>
  • Loading branch information
feloy authored Mar 6, 2024
1 parent a1381c0 commit 9906f2a
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 31 deletions.
88 changes: 88 additions & 0 deletions packages/main/src/plugin/kubernetes-context-state.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,94 @@ test('changing context should not start service informer on current context if n
expect(makeInformerMock).not.toHaveBeenCalled();
});

test('should not ignore events sent a short time before', async () => {
vi.useFakeTimers();
vi.mocked(makeInformer).mockImplementation(
(
kubeconfig: kubeclient.KubeConfig,
path: string,
_listPromiseFn: kubeclient.ListPromise<kubeclient.KubernetesObject>,
) => {
const connectResult = undefined;
switch (path) {
case '/apis/networking.k8s.io/v1/namespaces/ns1/ingresses':
return new FakeInformer(
kubeconfig.currentContext,
path,
0,
connectResult,
[
{
delayMs: 1,
verb: 'add',
object: {},
},
],
[],
);
case '/api/v1/namespaces/ns1/services':
return new FakeInformer(
kubeconfig.currentContext,
path,
0,
connectResult,
[
{
delayMs: 2,
verb: 'add',
object: {},
},
],
[],
);
}
return new FakeInformer(kubeconfig.currentContext, path, 0, connectResult, [], []);
},
);
const client = new ContextsManager(apiSender);
const kubeConfig = new kubeclient.KubeConfig();
const config = {
clusters: [
{
name: 'cluster1',
server: 'server1',
},
],
users: [
{
name: 'user1',
},
],
contexts: [
{
name: 'context1',
cluster: 'cluster1',
user: 'user1',
namespace: 'ns1',
},
{
name: 'context2',
cluster: 'cluster1',
user: 'user1',
namespace: 'ns2',
},
],
currentContext: 'context1',
};
kubeConfig.loadFromOptions(config);
client.registerGetCurrentContextResources('services');
client.registerGetCurrentContextResources('ingresses');
await client.update(kubeConfig);
vi.advanceTimersByTime(20);
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-contexts-general-state-update', expect.anything());
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-general-state-update', expect.anything());
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-pods-update', []);
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-deployments-update', []);
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-services-update', [{}]);
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-ingresses-update', [{}]);
expect(apiSenderSendMock).toHaveBeenCalledWith('kubernetes-current-context-routes-update', []);
});

describe('ContextsStates tests', () => {
test('hasInformer should check if informer exists for context', () => {
const client = new ContextsStates();
Expand Down
74 changes: 43 additions & 31 deletions packages/main/src/plugin/kubernetes-context-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,51 +772,63 @@ export class ContextsManager {
});
}

dispatchTimer: NodeJS.Timeout | undefined;
dispatchContextsGeneralStateTimer: NodeJS.Timeout | undefined;
dispatchCurrentContextGeneralStateTimer: NodeJS.Timeout | undefined;
dispatchCurrentContextResourceTimers = new Map<string, NodeJS.Timeout>();

private dispatch(options: DispatchOptions): void {
clearTimeout(this.dispatchTimer);
this.dispatchTimer = setTimeout(() => {
if (options.contextsGeneralState) {
this.dispatchContextsGeneralState();
}
if (options.currentContextGeneralState) {
this.dispatchCurrentContextGeneralState();
if (options.contextsGeneralState) {
this.dispatchDebounce(
`kubernetes-contexts-general-state-update`,
this.states.getContextsGeneralState(),
this.dispatchContextsGeneralStateTimer,
dispatchTimeout,
);
}
if (options.currentContextGeneralState) {
this.dispatchCurrentContextGeneralStateTimer = this.dispatchDebounce(
`kubernetes-current-context-general-state-update`,
this.states.getCurrentContextGeneralState(this.kubeConfig.currentContext),
this.dispatchCurrentContextGeneralStateTimer,
dispatchTimeout,
);
}
Object.keys(options.resources).forEach(res => {
const resname = res as ResourceName;
if (options.resources[resname]) {
this.dispatchCurrentContextResourceTimers.set(
resname,
this.dispatchDebounce(
`kubernetes-current-context-${resname}-update`,
this.states.getContextResources(this.kubeConfig.currentContext, resname),
this.dispatchCurrentContextResourceTimers.get(resname),
dispatchTimeout,
),
);
}
Object.keys(options.resources).forEach(res => {
const resname = res as ResourceName;
if (options.resources[resname]) {
this.dispatchCurrentContextResource(resname);
}
});
}, dispatchTimeout);
});
}

private dispatchContextsGeneralState(): void {
this.apiSender.send(`kubernetes-contexts-general-state-update`, this.states.getContextsGeneralState());
private dispatchDebounce(
eventName: string,
value: Map<string, ContextGeneralState> | ContextGeneralState | KubernetesObject[],
timer: NodeJS.Timeout | undefined,
timeout: number,
): NodeJS.Timeout {
clearTimeout(timer);
return setTimeout(() => {
this.apiSender.send(eventName, value);
}, timeout);
}

public getContextsGeneralState(): Map<string, ContextGeneralState> {
return this.states.getContextsGeneralState();
}

private dispatchCurrentContextGeneralState(): void {
this.apiSender.send(
`kubernetes-current-context-general-state-update`,
this.states.getCurrentContextGeneralState(this.kubeConfig.currentContext),
);
}

public getCurrentContextGeneralState(): ContextGeneralState {
return this.states.getCurrentContextGeneralState(this.kubeConfig.currentContext);
}

private dispatchCurrentContextResource(resourceName: ResourceName): void {
this.apiSender.send(
`kubernetes-current-context-${resourceName}-update`,
this.states.getContextResources(this.kubeConfig.currentContext, resourceName),
);
}

public registerGetCurrentContextResources(resourceName: ResourceName): KubernetesObject[] {
if (isSecondaryResourceName(resourceName)) {
this.secondaryWatchers.subscribe(resourceName);
Expand Down

0 comments on commit 9906f2a

Please sign in to comment.