Skip to content

Commit 13afefe

Browse files
bug: Sidecar would not be started when getClient was called due to previous abstraction (#324)
* Ensure sidecar starts correctly Signed-off-by: Xavier Geerinck <[email protected]> * Linting fixes Signed-off-by: Xavier Geerinck <[email protected]> * Resolve some comments Signed-off-by: Xavier Geerinck <[email protected]> * Give more time for timeout Signed-off-by: Xavier Geerinck <[email protected]> * Changes Signed-off-by: Xavier Geerinck <[email protected]>
1 parent d94ec34 commit 13afefe

File tree

8 files changed

+90
-39
lines changed

8 files changed

+90
-39
lines changed

package.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
"test:load": "jest --runInBand --detectOpenHandles",
99
"test:load:http": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- npm run test:load 'test/load'",
1010
"test:e2e": "jest --runInBand --detectOpenHandles",
11-
"test:e2e:all": "npm run test:e2e:grpc && npm run test:e2e:http && npm run test:e2e:http:actors",
12-
"test:e2e:grpc": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/(client).test.ts' ]",
13-
"test:e2e:http": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/(client|server).test.ts' ]",
14-
"test:e2e:http:actors": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/actors.test.ts' ]",
11+
"test:e2e:all": "npm run test:e2e:http; npm run test:e2e:grpc",
12+
"test:e2e:grpc": "npm run test:e2e:grpc:client && npm run test:e2e:grpc:server",
13+
"test:e2e:grpc:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/(client).test.ts' ]",
14+
"test:e2e:grpc:server": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/(server).test.ts' ]",
15+
"test:e2e:http": "npm run test:e2e:http:client && npm run test:e2e:http:server && npm run test:e2e:http:actors",
16+
"test:e2e:http:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/(client).test.ts' ]",
17+
"test:e2e:http:server": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/(server).test.ts' ]",
18+
"test:e2e:http:actors": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/actors.test.ts' ]",
1519
"test:unit": "jest --runInBand --detectOpenHandles",
1620
"test:unit:all": "npm run test:unit:main && npm run test:unit:actors && npm run test:unit:logger && npm run test:unit:utils",
1721
"test:unit:main": "NODE_ENV=test npm run test:unit 'test/unit/main/.*\\.test\\.ts'",
@@ -60,4 +64,4 @@
6064
"url": "https://github.com/dapr/js-sdk.git",
6165
"directory": ""
6266
}
63-
}
67+
}

src/implementation/Client/DaprClient.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ See the License for the specific language governing permissions and
1111
limitations under the License.
1212
*/
1313

14-
import * as NodeJSUtils from "../../utils/NodeJS.util";
15-
1614
import IClientBinding from '../../interfaces/Client/IClientBinding';
1715
import IClientPubSub from '../../interfaces/Client/IClientPubSub';
1816
import IClientState from '../../interfaces/Client/IClientState';
@@ -56,6 +54,7 @@ import { DaprClientOptions } from '../../types/DaprClientOptions';
5654
import { Settings } from '../../utils/Settings.util';
5755
import { Logger } from '../../logger/Logger';
5856
import GRPCClientProxy from "./GRPCClient/proxy";
57+
import * as NodeJSUtils from "../../utils/NodeJS.util";
5958
import { SDK_PACKAGE_NAME } from "../../version";
6059

6160
export default class DaprClient {
@@ -144,47 +143,44 @@ export default class DaprClient {
144143
return new DaprClient(client.getClientHost(), client.getClientPort(), client.getClientCommunicationProtocol(), client.getOptions());
145144
}
146145

147-
async stop(): Promise<void> {
148-
await this.daprClient.stop();
149-
}
146+
static async awaitSidecarStarted(fnIsSidecarStarted: () => Promise<boolean>): Promise<void> {
147+
const logger = new Logger("DaprClient", "DaprClient");
150148

151-
async awaitSidecarStarted(): Promise<void> {
152149
// Dapr will probe every 50ms to see if we are listening on our port: https://github.com/dapr/dapr/blob/a43712c97ead550ca2f733e9f7e7769ecb195d8b/pkg/runtime/runtime.go#L1694
153150
// if we are using actors we will change this to 4s to let the placement tables update
154-
let isHealthy = false;
155-
let isHealthyRetryCount = 0;
156-
const isHealthyMaxRetryCount = 60; // 1s startup delay and we try max for 60s
151+
let isStarted = await fnIsSidecarStarted();
152+
let isStartedRetryCount = 0;
153+
const isStartedMaxRetryCount = 60; // 1s startup delay and we try max for 60s
157154

158-
this.logger.info(`Awaiting Sidecar to be Started`);
159-
while (!isHealthy) {
160-
this.logger.verbose(`Waiting for the Dapr Sidecar to start, retry count: (#${isHealthyRetryCount})`);
155+
if (isStarted) {
156+
return;
157+
}
158+
159+
logger.info(`Awaiting Sidecar to be Started`);
160+
while (!isStarted) {
161+
logger.verbose(`Waiting for the Dapr Sidecar to start, retry count: (#${isStartedRetryCount})`);
161162
await NodeJSUtils.sleep(Settings.getDaprSidecarPollingDelayMs());
162163

163164
// Implement API call manually as we need to enable calling without initialization
164165
// everything routes through the `execute` method
165166
// to check health, we just ping the /metadata endpoint and see if we get a response
166-
isHealthy = await this.health.isHealthy();
167+
isStarted = await fnIsSidecarStarted();
167168

168169
// Finally, Handle the retry logic
169-
isHealthyRetryCount++;
170+
isStartedRetryCount++;
170171

171-
if (isHealthyRetryCount > isHealthyMaxRetryCount) {
172+
if (isStartedRetryCount > isStartedMaxRetryCount) {
172173
throw new Error("DAPR_SIDECAR_COULD_NOT_BE_STARTED");
173174
}
174175
}
175176
}
176177

177-
/**
178-
* Ensure the client is started, this takes care of:
179-
* 1. Making sure the sidecar is started
180-
* 2. Making sure the connection is established (e.g. in gRPC)
181-
* 3. Making sure the client is ready to be used
182-
*/
178+
async stop(): Promise<void> {
179+
await this.daprClient.stop();
180+
}
181+
183182
async start(): Promise<void> {
184-
await this.awaitSidecarStarted();
185183
await this.daprClient.start();
186-
await this.daprClient.setIsInitialized(true);
187-
this.logger.info("Sidecar Started");
188184
}
189185

190186
getDaprClient(): IClient {

src/implementation/Client/GRPCClient/GRPCClient.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,19 @@ limitations under the License.
1212
*/
1313

1414
import * as grpc from "@grpc/grpc-js";
15-
import { DaprClient } from "../../../proto/dapr/proto/runtime/v1/dapr_grpc_pb"
15+
import { DaprClient as GrpcDaprClient } from "../../../proto/dapr/proto/runtime/v1/dapr_grpc_pb"
1616
import IClient from "../../../interfaces/Client/IClient";
1717
import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum";
1818
import { DaprClientOptions } from "../../../types/DaprClientOptions";
1919
import { Settings } from '../../../utils/Settings.util';
2020
import { Logger } from "../../../logger/Logger";
21+
import GRPCClientSidecar from "./sidecar";
22+
import DaprClient from "../DaprClient";
2123

2224
export default class GRPCClient implements IClient {
2325
private isInitialized: boolean;
2426

25-
private readonly client: DaprClient;
27+
private readonly client: GrpcDaprClient;
2628
private readonly clientCredentials: grpc.ChannelCredentials;
2729
private readonly clientHost: string;
2830
private readonly clientPort: string;
@@ -53,7 +55,7 @@ export default class GRPCClient implements IClient {
5355
return this.clientPort;
5456
}
5557

56-
async getClient(requiresInitialization = true): Promise<DaprClient> {
58+
async getClient(requiresInitialization = true): Promise<GrpcDaprClient> {
5759
// Ensure the sidecar has been started
5860
if (!this.isInitialized && requiresInitialization) {
5961
await this.start();
@@ -70,8 +72,8 @@ export default class GRPCClient implements IClient {
7072
return this.clientCredentials;
7173
}
7274

73-
private generateClient(host: string, port: string, credentials: grpc.ChannelCredentials): DaprClient {
74-
const client = new DaprClient(`${host}:${port}`, credentials);
75+
private generateClient(host: string, port: string, credentials: grpc.ChannelCredentials): GrpcDaprClient {
76+
const client = new GrpcDaprClient(`${host}:${port}`, credentials);
7577
return client;
7678
}
7779

@@ -112,7 +114,18 @@ export default class GRPCClient implements IClient {
112114
})
113115
}
114116

117+
async _startAwaitSidecarStarted(): Promise<void> {
118+
await DaprClient.awaitSidecarStarted(async () => await GRPCClientSidecar.isStarted(this));
119+
}
120+
121+
/**
122+
* Ensure the client is started, this takes care of:
123+
* 1. Making sure the sidecar is started
124+
* 2. Making sure the connection is established (e.g. in gRPC)
125+
* 3. Making sure the client is ready to be used
126+
*/
115127
async start(): Promise<void> {
128+
await this._startAwaitSidecarStarted();
116129
await this._startWaitForClientReady();
117130
this.isInitialized = true;
118131
}

src/implementation/Client/GRPCClient/sidecar.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ limitations under the License.
1313

1414
import GRPCClient from './GRPCClient';
1515
import IClientSidecar from "../../../interfaces/Client/IClientSidecar";
16+
import { GetMetadataResponse } from '../../../proto/dapr/proto/runtime/v1/dapr_pb';
1617
import { Empty } from "google-protobuf/google/protobuf/empty_pb";
1718

1819
// https://docs.dapr.io/reference/api/secrets_api/
@@ -36,4 +37,22 @@ export default class GRPCClientSidecar implements IClientSidecar {
3637
});
3738
});
3839
}
40+
41+
static async isStarted(client: GRPCClient): Promise<boolean> {
42+
const callClient = await client.getClient(false);
43+
44+
return new Promise((resolve, _reject) => {
45+
try {
46+
callClient.getMetadata(new Empty(), (err, _res: GetMetadataResponse) => {
47+
if (err) {
48+
return resolve(false);
49+
}
50+
51+
return resolve(true);
52+
});
53+
} catch (_e) {
54+
return resolve(false);
55+
}
56+
});
57+
}
3958
}

src/implementation/Client/HTTPClient/HTTPClient.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ limitations under the License.
1212
*/
1313

1414
import fetch from "node-fetch";
15-
import { CommunicationProtocolEnum } from "../../..";
15+
import { CommunicationProtocolEnum, DaprClient } from "../../..";
1616
import IClient from "../../../interfaces/Client/IClient";
1717
import http from "http";
1818
import https from "https";
1919
import { DaprClientOptions } from "../../../types/DaprClientOptions";
2020
import { Settings } from '../../../utils/Settings.util';
2121
import { THTTPExecuteParams } from "../../../types/http/THTTPExecuteParams.type"
2222
import { Logger } from "../../../logger/Logger";
23+
import HTTPClientSidecar from "./sidecar";
2324

2425
export default class HTTPClient implements IClient {
2526
private isInitialized: boolean;
@@ -72,7 +73,8 @@ export default class HTTPClient implements IClient {
7273

7374
async getClient(requiresInitialization = true): Promise<typeof fetch> {
7475
// Ensure the sidecar has been started
75-
if (!this.isInitialized && requiresInitialization) {
76+
if (requiresInitialization && !this.isInitialized) {
77+
this.logger.verbose("Client is not initialized, starting sidecar and initializing");
7678
await this.start();
7779
}
7880

@@ -107,12 +109,21 @@ export default class HTTPClient implements IClient {
107109
return this.isInitialized;
108110
}
109111

112+
async _startAwaitSidecarStarted(): Promise<void> {
113+
await DaprClient.awaitSidecarStarted(async () => await HTTPClientSidecar.isStarted(this));
114+
}
115+
110116
async stop(): Promise<void> {
111117
this.httpAgent.destroy();
112118
this.httpsAgent.destroy();
113119
}
114120

115121
async start(): Promise<void> {
122+
await this._startAwaitSidecarStarted();
123+
this.isInitialized = true;
124+
125+
this.logger.info("Sidecar Started");
126+
116127
return;
117128
}
118129

src/implementation/Client/HTTPClient/sidecar.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,13 @@ export default class HTTPClientSidecar implements IClientSidecar {
2727
method: 'POST'
2828
});
2929
}
30+
31+
static async isStarted(client: HTTPClient): Promise<boolean> {
32+
try {
33+
const result = await client.execute(`/metadata`, null, false);
34+
return !!result;
35+
} catch (_e) {
36+
return false;
37+
}
38+
}
3039
}

src/implementation/Server/DaprServer.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,6 @@ export default class DaprServer {
113113

114114
// Ensure our sidecar starts and the client is ready
115115
await this.client.start();
116-
117-
// We are initialized
118-
this.logger.info("Sidecar Started");
119116
}
120117

121118
async stop(): Promise<void> {

test/e2e/grpc/server.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ describe('grpc/server', () => {
3939

4040
// Start server
4141
await server.start();
42+
43+
await new Promise((resolve, _reject) => setTimeout(resolve, 2500));
4244
}, 10 * 1000);
4345

4446
beforeEach(() => {

0 commit comments

Comments
 (0)