| ... | ... | @@ -220,49 +220,29 @@ const TorProtocolService = { | 
| 220 | 220 |    // Executes a command on the control port.
 | 
| 221 | 221 |    // Return a reply object or null if a fatal error occurs.
 | 
| 222 | 222 |    async sendCommand(cmd, args) {
 | 
| 223 |  | -    let conn, reply;
 | 
| 224 |  | -    const maxAttempts = 2;
 | 
| 225 |  | -    for (let attempt = 0; !reply && attempt < maxAttempts; attempt++) {
 | 
| 226 |  | -      try {
 | 
| 227 |  | -        conn = await this._getConnection();
 | 
| 228 |  | -        try {
 | 
| 229 |  | -          if (conn) {
 | 
| 230 |  | -            reply = await conn.sendCommand(cmd + (args ? " " + args : ""));
 | 
| 231 |  | -            if (reply) {
 | 
| 232 |  | -              // Return for reuse.
 | 
| 233 |  | -              this._returnConnection();
 | 
| 234 |  | -            } else {
 | 
| 235 |  | -              // Connection is bad.
 | 
| 236 |  | -              logger.warn(
 | 
| 237 |  | -                "sendCommand returned an empty response, taking the connection as broken and closing it."
 | 
| 238 |  | -              );
 | 
| 239 |  | -              this._closeConnection();
 | 
| 240 |  | -            }
 | 
| 241 |  | -          }
 | 
| 242 |  | -        } catch (e) {
 | 
| 243 |  | -          logger.error(`Cannot send the command ${cmd}`, e);
 | 
| 244 |  | -          this._closeConnection();
 | 
| 245 |  | -        }
 | 
| 246 |  | -      } catch (e) {
 | 
| 247 |  | -        logger.error("Cannot get a connection to the control port", e);
 | 
|  | 223 | +    const maxTimeout = 1000;
 | 
|  | 224 | +    let leftConnAttempts = 5;
 | 
|  | 225 | +    let timeout = 250;
 | 
|  | 226 | +    let reply;
 | 
|  | 227 | +    while (leftConnAttempts-- > 0) {
 | 
|  | 228 | +      const response = await this._trySend(cmd, args, leftConnAttempts == 0);
 | 
|  | 229 | +      if (response.connected) {
 | 
|  | 230 | +        reply = response.reply;
 | 
|  | 231 | +        break;
 | 
| 248 | 232 |        }
 | 
| 249 |  | -    }
 | 
| 250 |  | -
 | 
| 251 |  | -    // We failed to acquire the controller after multiple attempts.
 | 
| 252 |  | -    // Try again after some time.
 | 
| 253 |  | -    if (!conn) {
 | 
| 254 |  | -      logger.info(
 | 
| 255 |  | -        "sendCommand: Acquiring control connection failed",
 | 
|  | 233 | +      // We failed to acquire the controller after multiple attempts.
 | 
|  | 234 | +      // Try again after some time.
 | 
|  | 235 | +      logger.warn(
 | 
|  | 236 | +        "sendCommand: Acquiring control connection failed, trying again later.",
 | 
| 256 | 237 |          cmd,
 | 
| 257 | 238 |          args
 | 
| 258 | 239 |        );
 | 
| 259 |  | -      return new Promise(resolve =>
 | 
| 260 |  | -        setTimeout(() => {
 | 
| 261 |  | -          resolve(this.sendCommand(cmd, args));
 | 
| 262 |  | -        }, 250)
 | 
| 263 |  | -      );
 | 
|  | 240 | +      await new Promise(resolve => setTimeout(() => resolve(), timeout));
 | 
|  | 241 | +      timeout = Math.min(2 * timeout, maxTimeout);
 | 
| 264 | 242 |      }
 | 
| 265 | 243 |  
 | 
|  | 244 | +    // We sent the command, but we still got an empty response.
 | 
|  | 245 | +    // Something must be busted elsewhere.
 | 
| 266 | 246 |      if (!reply) {
 | 
| 267 | 247 |        throw new Error(`${cmd} sent an empty response`);
 | 
| 268 | 248 |      }
 | 
| ... | ... | @@ -590,6 +570,48 @@ const TorProtocolService = { | 
| 590 | 570 |      }
 | 
| 591 | 571 |    },
 | 
| 592 | 572 |  
 | 
|  | 573 | +  async _trySend(cmd, args, rethrow) {
 | 
|  | 574 | +    let connected = false;
 | 
|  | 575 | +    let reply;
 | 
|  | 576 | +    let leftAttempts = 2;
 | 
|  | 577 | +    while (leftAttempts-- > 0) {
 | 
|  | 578 | +      let conn;
 | 
|  | 579 | +      try {
 | 
|  | 580 | +        conn = await this._getConnection();
 | 
|  | 581 | +      } catch (e) {
 | 
|  | 582 | +        logger.error("Cannot get a connection to the control port", e);
 | 
|  | 583 | +        if (leftAttempts == 0 && rethrow) {
 | 
|  | 584 | +          throw e;
 | 
|  | 585 | +        }
 | 
|  | 586 | +      }
 | 
|  | 587 | +      if (!conn) {
 | 
|  | 588 | +        continue;
 | 
|  | 589 | +      }
 | 
|  | 590 | +      // If we _ever_ got a connection, the caller should not try again
 | 
|  | 591 | +      connected = true;
 | 
|  | 592 | +      try {
 | 
|  | 593 | +        reply = await conn.sendCommand(cmd + (args ? " " + args : ""));
 | 
|  | 594 | +        if (reply) {
 | 
|  | 595 | +          // Return for reuse.
 | 
|  | 596 | +          this._returnConnection();
 | 
|  | 597 | +        } else {
 | 
|  | 598 | +          // Connection is bad.
 | 
|  | 599 | +          logger.warn(
 | 
|  | 600 | +            "sendCommand returned an empty response, taking the connection as broken and closing it."
 | 
|  | 601 | +          );
 | 
|  | 602 | +          this._closeConnection();
 | 
|  | 603 | +        }
 | 
|  | 604 | +      } catch (e) {
 | 
|  | 605 | +        logger.error(`Cannot send the command ${cmd}`, e);
 | 
|  | 606 | +        this._closeConnection();
 | 
|  | 607 | +        if (leftAttempts == 0 && rethrow) {
 | 
|  | 608 | +          throw e;
 | 
|  | 609 | +        }
 | 
|  | 610 | +      }
 | 
|  | 611 | +    }
 | 
|  | 612 | +    return { connected, reply };
 | 
|  | 613 | +  },
 | 
|  | 614 | +
 | 
| 593 | 615 |    // Opens an authenticated connection, sets it to this._controlConnection, and
 | 
| 594 | 616 |    // return it.
 | 
| 595 | 617 |    async _getConnection() {
 |