Source: lib/core/error.js

  1. 'use strict';
  2. const mongoErrorContextSymbol = Symbol('mongoErrorContextSymbol');
  3. const maxWireVersion = require('./utils').maxWireVersion;
  4. /**
  5. * Creates a new MongoError
  6. *
  7. * @augments Error
  8. * @param {Error|string|object} message The error message
  9. * @property {string} message The error message
  10. * @property {string} stack The error call stack
  11. */
  12. class MongoError extends Error {
  13. constructor(message) {
  14. if (message instanceof Error) {
  15. super(message.message);
  16. this.stack = message.stack;
  17. } else {
  18. if (typeof message === 'string') {
  19. super(message);
  20. } else {
  21. super(message.message || message.errmsg || message.$err || 'n/a');
  22. for (var name in message) {
  23. this[name] = message[name];
  24. }
  25. }
  26. Error.captureStackTrace(this, this.constructor);
  27. }
  28. this.name = 'MongoError';
  29. this[mongoErrorContextSymbol] = this[mongoErrorContextSymbol] || {};
  30. }
  31. /**
  32. * Creates a new MongoError object
  33. *
  34. * @param {Error|string|object} options The options used to create the error.
  35. * @return {MongoError} A MongoError instance
  36. * @deprecated Use `new MongoError()` instead.
  37. */
  38. static create(options) {
  39. return new MongoError(options);
  40. }
  41. /**
  42. * Checks the error to see if it has an error label
  43. * @param {string} label The error label to check for
  44. * @returns {boolean} returns true if the error has the provided error label
  45. */
  46. hasErrorLabel(label) {
  47. return this.errorLabels && this.errorLabels.indexOf(label) !== -1;
  48. }
  49. }
  50. /**
  51. * An error indicating an issue with the network, including TCP
  52. * errors and timeouts.
  53. *
  54. * @param {Error|string|object} message The error message
  55. * @property {string} message The error message
  56. * @property {string} stack The error call stack
  57. * @extends MongoError
  58. */
  59. class MongoNetworkError extends MongoError {
  60. constructor(message) {
  61. super(message);
  62. this.name = 'MongoNetworkError';
  63. }
  64. }
  65. /**
  66. * An error used when attempting to parse a value (like a connection string)
  67. *
  68. * @param {Error|string|object} message The error message
  69. * @property {string} message The error message
  70. * @extends MongoError
  71. */
  72. class MongoParseError extends MongoError {
  73. constructor(message) {
  74. super(message);
  75. this.name = 'MongoParseError';
  76. }
  77. }
  78. /**
  79. * An error signifying a client-side timeout event
  80. *
  81. * @param {Error|string|object} message The error message
  82. * @param {string|object} [reason] The reason the timeout occured
  83. * @property {string} message The error message
  84. * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
  85. * @extends MongoError
  86. */
  87. class MongoTimeoutError extends MongoError {
  88. constructor(message, reason) {
  89. super(message);
  90. this.name = 'MongoTimeoutError';
  91. if (reason != null) {
  92. this.reason = reason;
  93. }
  94. }
  95. }
  96. function makeWriteConcernResultObject(input) {
  97. const output = Object.assign({}, input);
  98. if (output.ok === 0) {
  99. output.ok = 1;
  100. delete output.errmsg;
  101. delete output.code;
  102. delete output.codeName;
  103. }
  104. return output;
  105. }
  106. /**
  107. * An error thrown when the server reports a writeConcernError
  108. *
  109. * @param {Error|string|object} message The error message
  110. * @param {object} result The result document (provided if ok: 1)
  111. * @property {string} message The error message
  112. * @property {object} [result] The result document (provided if ok: 1)
  113. * @extends MongoError
  114. */
  115. class MongoWriteConcernError extends MongoError {
  116. constructor(message, result) {
  117. super(message);
  118. this.name = 'MongoWriteConcernError';
  119. if (result != null) {
  120. this.result = makeWriteConcernResultObject(result);
  121. }
  122. }
  123. }
  124. // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
  125. const RETRYABLE_ERROR_CODES = new Set([
  126. 6, // HostUnreachable
  127. 7, // HostNotFound
  128. 89, // NetworkTimeout
  129. 91, // ShutdownInProgress
  130. 189, // PrimarySteppedDown
  131. 9001, // SocketException
  132. 10107, // NotMaster
  133. 11600, // InterruptedAtShutdown
  134. 11602, // InterruptedDueToReplStateChange
  135. 13435, // NotMasterNoSlaveOk
  136. 13436 // NotMasterOrSecondary
  137. ]);
  138. /**
  139. * Determines whether an error is something the driver should attempt to retry
  140. *
  141. * @ignore
  142. * @param {MongoError|Error} error
  143. */
  144. function isRetryableError(error) {
  145. return (
  146. RETRYABLE_ERROR_CODES.has(error.code) ||
  147. error instanceof MongoNetworkError ||
  148. error.message.match(/not master/) ||
  149. error.message.match(/node is recovering/)
  150. );
  151. }
  152. const SDAM_RECOVERING_CODES = new Set([
  153. 91, // ShutdownInProgress
  154. 189, // PrimarySteppedDown
  155. 11600, // InterruptedAtShutdown
  156. 11602, // InterruptedDueToReplStateChange
  157. 13436 // NotMasterOrSecondary
  158. ]);
  159. const SDAM_NOTMASTER_CODES = new Set([
  160. 10107, // NotMaster
  161. 13435 // NotMasterNoSlaveOk
  162. ]);
  163. const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
  164. 11600, // InterruptedAtShutdown
  165. 91 // ShutdownInProgress
  166. ]);
  167. function isRecoveringError(err) {
  168. if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
  169. return true;
  170. }
  171. return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
  172. }
  173. function isNotMasterError(err) {
  174. if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
  175. return true;
  176. }
  177. if (isRecoveringError(err)) {
  178. return false;
  179. }
  180. return err.message.match(/not master/);
  181. }
  182. function isNodeShuttingDownError(err) {
  183. return err.code && SDAM_NODE_SHUTTING_DOWN_ERROR_CODES.has(err.code);
  184. }
  185. /**
  186. * Determines whether SDAM can recover from a given error. If it cannot
  187. * then the pool will be cleared, and server state will completely reset
  188. * locally.
  189. *
  190. * @ignore
  191. * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
  192. * @param {MongoError|Error} error
  193. * @param {Server} server
  194. */
  195. function isSDAMUnrecoverableError(error, server) {
  196. // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
  197. // close event are considered unrecoverable
  198. if (error instanceof MongoParseError || error == null) {
  199. return true;
  200. }
  201. if (isRecoveringError(error) || isNotMasterError(error)) {
  202. if (maxWireVersion(server) >= 8 && !isNodeShuttingDownError(error)) {
  203. return false;
  204. }
  205. return true;
  206. }
  207. return false;
  208. }
  209. module.exports = {
  210. MongoError,
  211. MongoNetworkError,
  212. MongoParseError,
  213. MongoTimeoutError,
  214. MongoWriteConcernError,
  215. mongoErrorContextSymbol,
  216. isRetryableError,
  217. isSDAMUnrecoverableError
  218. };