Skip to content

Commit 5db8203

Browse files
Merge pull request #272 from XavierGeerinck/issue_268
feat: Implement `await client.start()` to ensure sidecar has started
2 parents ff8e605 + d75efb4 commit 5db8203

File tree

25 files changed

+490
-423
lines changed

25 files changed

+490
-423
lines changed

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,13 @@ const daprPort = "50000"; // Dapr Sidecar Port
5252
const serverHost = "127.0.0.1"; // App Host of this Example Server
5353
const serverPort = "50001"; // App Port of this Example Server
5454

55-
// Create a Server (will subscribe) and Client (will publish)
55+
// Create a Server (will subscribe)
5656
const server = new DaprServer(serverHost, serverPort, daprHost, daprPort);
57-
const client = new DaprClient(daprHost, daprPort);
58-
59-
// Initialize the server to subscribe (listen)
6057
await server.pubsub.subscribe("my-pubsub-component", "my-topic", async (data: any) => console.log(`Received: ${JSON.stringify(data)}`));
6158
await server.start();
6259

63-
// Send a message
60+
// Create a Client (will publish)
61+
const client = new DaprClient(daprHost, daprPort);
6462
await client.pubsub.publish("my-pubsub-component", "my-topic", { hello: "world" });
6563
```
6664

daprdocs/content/en/js-sdk-docs/js-client/_index.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,7 @@ const daprAppId = "example-config";
347347

348348
async function start() {
349349

350-
const client = new DaprClient(
351-
daprHost,
352-
process.env.DAPR_HTTP_PORT
353-
);
350+
const client = new DaprClient(daprHost, process.env.DAPR_HTTP_PORT);
354351

355352
const config = await client.configuration.get('config-store', ['key1', 'key2']);
356353
console.log(config);

documentation/development.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ npm run test:unit:main
4444
npm run test:unit:actors
4545

4646
# Start gRPC tests
47-
npm run test:e2e:grpc:main
47+
npm run test:e2e:grpc
4848

4949
# Start HTTP tests
50-
npm run test:e2e:http:main
51-
npm run test:e2e:http:actors
50+
npm run test:e2e:http
5251
```
5352

5453
## Publishing

examples/configuration/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const daprHost = "127.0.0.1";
1717
const daprPortDefault = "3500";
1818

1919
async function start() {
20-
2120
const client = new DaprClient(
2221
daprHost,
2322
process.env.DAPR_HTTP_PORT ?? daprPortDefault

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
"scripts": {
77
"test": "npm run test:unit:all && npm run test:e2e:all",
88
"test:load": "jest --runInBand --detectOpenHandles",
9-
"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'",
9+
"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:main && npm run test:e2e:http:main && npm run test:e2e:http:actors",
12-
"test:e2e:grpc:main": "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 npm run test:e2e 'test/e2e/main.grpc.test.ts'",
13-
"test:e2e:http:main": "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:e2e 'test/e2e/main.http.test.ts'",
14-
"test:e2e:http:actors": "dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components npm run test:e2e 'test/e2e/actors.http.test.ts'",
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|server).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' ]",
1515
"test:unit": "jest --runInBand --detectOpenHandles",
1616
"test:unit:all": "npm run test:unit:main && npm run test:unit:actors && npm run test:unit:utils",
1717
"test:unit:main": "NODE_ENV=test npm run test:unit 'test/unit/main/.*\\.test\\.ts'",

scripts/test-e2e.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515

1616
# Start gRPC tests
1717
echo "Running gRPC tests"
18-
npm run test:e2e:grpc:main
18+
npm run test:e2e:grpc
1919

2020
# Start HTTP tests
2121
echo "Running HTTP tests"
22-
npm run test:e2e:http:main
23-
npm run test:e2e:http:actors
22+
npm run test:e2e:http

scripts/test-init.sh

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/implementation/Client/DaprClient.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
See the License for the specific language governing permissions and
1111
limitations under the License.
1212
*/
13+
import * as NodeJSUtils from "../../utils/NodeJS.util";
1314

1415
import IClientBinding from '../../interfaces/Client/IClientBinding';
1516
import IClientPubSub from '../../interfaces/Client/IClientPubSub';
@@ -133,6 +134,45 @@ export default class DaprClient {
133134
await this.daprClient.stop();
134135
}
135136

137+
async awaitSidecarStarted(): Promise<void> {
138+
// 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
139+
// if we are using actors we will change this to 4s to let the placement tables update
140+
let isHealthy = false;
141+
let isHealthyRetryCount = 0;
142+
const isHealthyMaxRetryCount = 60; // 1s startup delay and we try max for 60s
143+
144+
console.log(`[Dapr-JS][Client] Awaiting Sidecar to be Started`);
145+
while (!isHealthy) {
146+
console.log(`[Dapr-JS][Client] Waiting till Dapr Sidecar Started (#${isHealthyRetryCount})`);
147+
await NodeJSUtils.sleep(Settings.getDaprSidecarPollingDelayMs());
148+
149+
// Implement API call manually as we need to enable calling without initialization
150+
// everything routes through the `execute` method
151+
// to check health, we just ping the /metadata endpoint and see if we get a response
152+
isHealthy = await this.health.isHealthy();
153+
154+
// Finally, Handle the retry logic
155+
isHealthyRetryCount++;
156+
157+
if (isHealthyRetryCount > isHealthyMaxRetryCount) {
158+
throw new Error("DAPR_SIDECAR_COULD_NOT_BE_STARTED");
159+
}
160+
}
161+
}
162+
163+
/**
164+
* Ensure the client is started, this takes care of:
165+
* 1. Making sure the sidecar is started
166+
* 2. Making sure the connection is established (e.g. in gRPC)
167+
* 3. Making sure the client is ready to be used
168+
*/
169+
async start(): Promise<void> {
170+
await this.awaitSidecarStarted();
171+
await this.daprClient.start();
172+
await this.daprClient.setIsInitialized(true);
173+
console.log(`[Dapr-JS][Client] Sidecar Started`);
174+
}
175+
136176
getDaprClient(): IClient {
137177
return this.daprClient;
138178
}

src/implementation/Client/GRPCClient/GRPCClient.ts

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

1414
import * as grpc from "@grpc/grpc-js";
15-
import { ChannelCredentials } from "@grpc/grpc-js";
1615
import { DaprClient } from "../../../proto/dapr/proto/runtime/v1/dapr_grpc_pb"
1716
import IClient from "../../../interfaces/Client/IClient";
1817
import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum";
1918
import { DaprClientOptions } from "../../../types/DaprClientOptions";
2019
import { Settings } from '../../../utils/Settings.util';
2120

2221
export default class GRPCClient implements IClient {
22+
private isInitialized: boolean;
23+
2324
private readonly client: DaprClient;
2425
private readonly clientCredentials: grpc.ChannelCredentials;
2526
private readonly clientHost: string;
@@ -35,8 +36,9 @@ export default class GRPCClient implements IClient {
3536
) {
3637
this.clientHost = host;
3738
this.clientPort = port;
38-
this.clientCredentials = ChannelCredentials.createInsecure();
39+
this.clientCredentials = grpc.ChannelCredentials.createInsecure();
3940
this.options = options;
41+
this.isInitialized = false;
4042

4143
console.log(`[Dapr-JS][gRPC] Opening connection to ${this.clientHost}:${this.clientPort}`);
4244
this.client = new DaprClient(`${this.clientHost}:${this.clientPort}`, this.clientCredentials);
@@ -62,7 +64,30 @@ export default class GRPCClient implements IClient {
6264
return this.options;
6365
}
6466

67+
setIsInitialized(isInitialized: boolean): void {
68+
this.isInitialized = isInitialized;
69+
}
70+
6571
async stop(): Promise<void> {
6672
this.client.close();
6773
}
74+
75+
async _startWaitForClientReady(): Promise<void> {
76+
const deadline = Date.now() + Settings.getDaprSidecarStartupTimeoutMs();
77+
78+
return new Promise((resolve, reject) => {
79+
this.client.waitForReady(deadline, (err?) => {
80+
if (err) {
81+
console.error(err);
82+
return reject();
83+
}
84+
85+
return resolve();
86+
});
87+
})
88+
}
89+
90+
async start(): Promise<void> {
91+
await this._startWaitForClientReady();
92+
}
6893
}

0 commit comments

Comments
 (0)