slackapi / node-slack-sdk

Slack Developer Kit for Node.js
https://tools.slack.dev/node-slack-sdk/
MIT License
3.27k stars 662 forks source link

[BUG] RTMClient unable to RTM start: Generator is already running #571

Closed mousemke closed 6 years ago

mousemke commented 6 years ago

Description

i just updated @slack/client to v4.2.2 (from v3.5.1) and am having an issue i cant find an answer to. it worked on my local machine, but deployed to ec2 it gives me:

[INFO] @slack/client:RTMClient unable to RTM start: Generator is already running

What type of issue is this? (place an x in one of the [ ])

Requirements (place an x in each of the [ ])


Bug Report

i just updated @slack/client to v4 and am having an issue i cant find an answer to. it worked on my local machine, but deployed to ec2 it gives me:

[INFO] @slack/client:RTMClient unable to RTM start: Generator is already running

Reproducible in:

@slack/client version: 4.2.2

node version: 8.11

OS version(s): ami id ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20160114.5 (ami-f95ef58a)

Steps to reproduce:

  1. install project
  2. npm i
  3. run

Expected result:

slack client starts

Actual result:

slack client doesnt start

Attachments:

actual code where it fails. https://github.com/mousemke/_val/blob/dev/modules/core/slack.js

if i comment out the _bot.start(), the error ofc goes away, but also ofc the service doesnt start

aoberoi commented 6 years ago

that's a new one! sorry about the issue you're having. i think its especially interesting that it works locally but not on AWS.

it may take me some time to reproduce this one since i don't have an AWS instance to play with, so if you can narrow down the reproduction scenario to something a little easier to get to, that would be swell!

i took a look at the transpiled output from typescript, and i presume the generator function the error message is referring to is implementation below. (RTMClient depends on WebClient, so its actually located in WebClient#apiCall())

    apiCall(method, options, callback) {
        this.logger.debug('apiCall() start');
        // The following thunk is the actual implementation for this method. It is wrapped so that it can be adapted for
        // different executions below.
        const implementation = () => __awaiter(this, void 0, void 0, function* () {
            if (typeof options === 'string' || typeof options === 'number' || typeof options === 'boolean') {
                throw new TypeError(`Expected an options argument but instead received a ${typeof options}`);
            }
            const requestBody = this.serializeApiCallOptions(Object.assign({ token: this.token }, options));
            // The following thunk encapsulates the task so that it can be coordinated for retries
            const task = () => {
                this.logger.debug('request attempt');
                return got.post(urlJoin(this.slackApiUrl, method), 
                // @ts-ignore using older definitions for package `got`, can remove when type `@types/got` is updated for v8
                Object.assign({
                    form: !canBodyBeFormMultipart(requestBody),
                    body: requestBody,
                    retries: 0,
                    headers: {
                        'user-agent': this.userAgent,
                    },
                    agent: this.agentConfig,
                }, this.tlsConfig))
                    .catch((error) => {
                    // Wrap errors in this packages own error types (abstract the implementation details' types)
                    if (error.name === 'RequestError') {
                        throw requestErrorWithOriginal(error);
                    }
                    else if (error.name === 'ReadError') {
                        throw readErrorWithOriginal(error);
                    }
                    else if (error.name === 'HTTPError') {
                        throw httpErrorWithOriginal(error);
                    }
                    else {
                        throw error;
                    }
                })
                    .then((response) => {
                    const result = this.buildResult(response);
                    // log warnings in response metadata
                    if (result.response_metadata !== undefined && result.response_metadata.warnings !== undefined) {
                        result.response_metadata.warnings.forEach(this.logger.warn);
                    }
                    // handle rate-limiting
                    if (response.statusCode !== undefined && response.statusCode === 429) {
                        const retryAfterMs = result.retryAfter !== undefined ? result.retryAfter : (60 * 1000);
                        // NOTE: the following event could have more information regarding the api call that is being delayed
                        this.emit('rate_limited', retryAfterMs / 1000);
                        this.logger.info(`API Call failed due to rate limiting. Will retry in ${retryAfterMs / 1000} seconds.`);
                        // wait and return the result from calling `task` again after the specified number of seconds
                        return delay(retryAfterMs).then(task);
                    }
                    // For any error in the API response, treat them as irrecoverable by throwing an AbortError to end retries.
                    if (!result.ok) {
                        const error = errors_1.errorWithCode(new Error(`An API error occurred: ${result.error}`), errors_1.ErrorCode.PlatformError);
                        error.data = result;
                        throw new pRetry.AbortError(error);
                    }
                    return result;
                });
            };
            // The following thunk encapsulates the retried task so that it can be coordinated for request queuing
            const taskAfterRetries = () => pRetry(task, this.retryConfig);
            // The final return value is the resolution of the task after being retried and queued
            return this.requestQueue.add(taskAfterRetries);
        });
        // Adapt the interface for callback-based execution or Promise-based execution
        if (callback !== undefined) {
            util_1.callbackify(implementation)(callback);
            return;
        }
        return implementation();
    }
aoberoi commented 6 years ago

@mousemke would you be able to turn on the debug logging and let me know what the program's output looks like? you just need to change the instantiation to const _bot = new RTMClient( token, { logLevel: LogLevel.DEBUG });. LogLevel is available as an export from the top-level, so you can prob just add that to your destructuring assignment.

aoberoi commented 6 years ago

i think i might know the cause. it looks like you've enabled experimental V8 features using the --harmony flag: https://github.com/mousemke/_val/blob/3704d807ead47b376fda745c74c09bef394faf3c/package.json#L9

i found the following typescript issue, which links to the following V8 issue. it concludes as closing because the --harmony behavior is known to be broken but the stable generator support works. if you have --harmony turned on for some other JavaScript feature, it may be worth looking into whether a more specific flag for that feature exists, or if it can be eliminated.

mousemke commented 6 years ago

@aoberoi perfection. Harmony removed. Works like a charm. I appreciate the help!

aoberoi commented 6 years ago

🙌