Source: lib/core/error.js

  1. 'use strict';
  2. const kErrorLabels = Symbol('errorLabels');
  3. /**
  4. * Creates a new MongoError
  5. *
  6. * @augments Error
  7. * @param {Error|string|object} message The error message
  8. * @property {string} message The error message
  9. * @property {string} stack The error call stack
  10. */
  11. class MongoError extends Error {
  12. constructor(message) {
  13. if (message instanceof Error) {
  14. super(message.message);
  15. this.stack = message.stack;
  16. } else {
  17. if (typeof message === 'string') {
  18. super(message);
  19. } else {
  20. super(message.message || message.errmsg || message.$err || 'n/a');
  21. if (message.errorLabels) {
  22. this[kErrorLabels] = new Set(message.errorLabels);
  23. }
  24. for (var name in message) {
  25. if (name === 'errorLabels' || name === 'errmsg') {
  26. continue;
  27. }
  28. this[name] = message[name];
  29. }
  30. }
  31. Error.captureStackTrace(this, this.constructor);
  32. }
  33. this.name = 'MongoError';
  34. }
  35. /**
  36. * Legacy name for server error responses
  37. */
  38. get errmsg() {
  39. return this.message;
  40. }
  41. /**
  42. * Creates a new MongoError object
  43. *
  44. * @param {Error|string|object} options The options used to create the error.
  45. * @return {MongoError} A MongoError instance
  46. * @deprecated Use `new MongoError()` instead.
  47. */
  48. static create(options) {
  49. return new MongoError(options);
  50. }
  51. /**
  52. * Checks the error to see if it has an error label
  53. * @param {string} label The error label to check for
  54. * @returns {boolean} returns true if the error has the provided error label
  55. */
  56. hasErrorLabel(label) {
  57. if (this[kErrorLabels] == null) {
  58. return false;
  59. }
  60. return this[kErrorLabels].has(label);
  61. }
  62. addErrorLabel(label) {
  63. if (this[kErrorLabels] == null) {
  64. this[kErrorLabels] = new Set();
  65. }
  66. this[kErrorLabels].add(label);
  67. }
  68. get errorLabels() {
  69. return this[kErrorLabels] ? Array.from(this[kErrorLabels]) : [];
  70. }
  71. }
  72. const kBeforeHandshake = Symbol('beforeHandshake');
  73. function isNetworkErrorBeforeHandshake(err) {
  74. return err[kBeforeHandshake] === true;
  75. }
  76. /**
  77. * An error indicating an issue with the network, including TCP
  78. * errors and timeouts.
  79. *
  80. * @param {Error|string|object} message The error message
  81. * @property {string} message The error message
  82. * @property {string} stack The error call stack
  83. * @extends MongoError
  84. */
  85. class MongoNetworkError extends MongoError {
  86. constructor(message, options) {
  87. super(message);
  88. this.name = 'MongoNetworkError';
  89. if (options && options.beforeHandshake === true) {
  90. this[kBeforeHandshake] = true;
  91. }
  92. }
  93. }
  94. /**
  95. * An error indicating a network timeout occurred
  96. *
  97. * @param {Error|string|object} message The error message
  98. * @property {string} message The error message
  99. * @property {object} [options.beforeHandshake] Indicates the timeout happened before a connection handshake completed
  100. * @extends MongoError
  101. */
  102. class MongoNetworkTimeoutError extends MongoNetworkError {
  103. constructor(message, options) {
  104. super(message, options);
  105. this.name = 'MongoNetworkTimeoutError';
  106. }
  107. }
  108. /**
  109. * An error used when attempting to parse a value (like a connection string)
  110. *
  111. * @param {Error|string|object} message The error message
  112. * @property {string} message The error message
  113. * @extends MongoError
  114. */
  115. class MongoParseError extends MongoError {
  116. constructor(message) {
  117. super(message);
  118. this.name = 'MongoParseError';
  119. }
  120. }
  121. /**
  122. * An error signifying a client-side timeout event
  123. *
  124. * @param {Error|string|object} message The error message
  125. * @param {string|object} [reason] The reason the timeout occured
  126. * @property {string} message The error message
  127. * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
  128. * @extends MongoError
  129. */
  130. class MongoTimeoutError extends MongoError {
  131. constructor(message, reason) {
  132. if (reason && reason.error) {
  133. super(reason.error.message || reason.error);
  134. } else {
  135. super(message);
  136. }
  137. this.name = 'MongoTimeoutError';
  138. if (reason) {
  139. this.reason = reason;
  140. }
  141. }
  142. }
  143. /**
  144. * An error signifying a client-side server selection error
  145. *
  146. * @param {Error|string|object} message The error message
  147. * @param {string|object} [reason] The reason the timeout occured
  148. * @property {string} message The error message
  149. * @property {string} [reason] An optional reason context for the timeout, generally an error saved during flow of monitoring and selecting servers
  150. * @extends MongoError
  151. */
  152. class MongoServerSelectionError extends MongoTimeoutError {
  153. constructor(message, reason) {
  154. super(message, reason);
  155. this.name = 'MongoServerSelectionError';
  156. }
  157. }
  158. function makeWriteConcernResultObject(input) {
  159. const output = Object.assign({}, input);
  160. if (output.ok === 0) {
  161. output.ok = 1;
  162. delete output.errmsg;
  163. delete output.code;
  164. delete output.codeName;
  165. }
  166. return output;
  167. }
  168. /**
  169. * An error thrown when the server reports a writeConcernError
  170. *
  171. * @param {Error|string|object} message The error message
  172. * @param {object} result The result document (provided if ok: 1)
  173. * @property {string} message The error message
  174. * @property {object} [result] The result document (provided if ok: 1)
  175. * @extends MongoError
  176. */
  177. class MongoWriteConcernError extends MongoError {
  178. constructor(message, result) {
  179. super(message);
  180. this.name = 'MongoWriteConcernError';
  181. if (result && Array.isArray(result.errorLabels)) {
  182. this[kErrorLabels] = new Set(result.errorLabels);
  183. }
  184. if (result != null) {
  185. this.result = makeWriteConcernResultObject(result);
  186. }
  187. }
  188. }
  189. // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
  190. const RETRYABLE_ERROR_CODES = new Set([
  191. 6, // HostUnreachable
  192. 7, // HostNotFound
  193. 89, // NetworkTimeout
  194. 91, // ShutdownInProgress
  195. 189, // PrimarySteppedDown
  196. 9001, // SocketException
  197. 10107, // NotMaster
  198. 11600, // InterruptedAtShutdown
  199. 11602, // InterruptedDueToReplStateChange
  200. 13435, // NotMasterNoSlaveOk
  201. 13436 // NotMasterOrSecondary
  202. ]);
  203. const RETRYABLE_WRITE_ERROR_CODES = new Set([
  204. 11600, // InterruptedAtShutdown
  205. 11602, // InterruptedDueToReplStateChange
  206. 10107, // NotMaster
  207. 13435, // NotMasterNoSlaveOk
  208. 13436, // NotMasterOrSecondary
  209. 189, // PrimarySteppedDown
  210. 91, // ShutdownInProgress
  211. 7, // HostNotFound
  212. 6, // HostUnreachable
  213. 89, // NetworkTimeout
  214. 9001, // SocketException
  215. 262 // ExceededTimeLimit
  216. ]);
  217. function isRetryableWriteError(error) {
  218. if (error instanceof MongoWriteConcernError) {
  219. return (
  220. RETRYABLE_WRITE_ERROR_CODES.has(error.code) ||
  221. RETRYABLE_WRITE_ERROR_CODES.has(error.result.code)
  222. );
  223. }
  224. return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
  225. }
  226. /**
  227. * Determines whether an error is something the driver should attempt to retry
  228. *
  229. * @ignore
  230. * @param {MongoError|Error} error
  231. */
  232. function isRetryableError(error) {
  233. return (
  234. RETRYABLE_ERROR_CODES.has(error.code) ||
  235. error instanceof MongoNetworkError ||
  236. error.message.match(/not master/) ||
  237. error.message.match(/node is recovering/)
  238. );
  239. }
  240. const SDAM_RECOVERING_CODES = new Set([
  241. 91, // ShutdownInProgress
  242. 189, // PrimarySteppedDown
  243. 11600, // InterruptedAtShutdown
  244. 11602, // InterruptedDueToReplStateChange
  245. 13436 // NotMasterOrSecondary
  246. ]);
  247. const SDAM_NOTMASTER_CODES = new Set([
  248. 10107, // NotMaster
  249. 13435 // NotMasterNoSlaveOk
  250. ]);
  251. const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set([
  252. 11600, // InterruptedAtShutdown
  253. 91 // ShutdownInProgress
  254. ]);
  255. function isRecoveringError(err) {
  256. if (err.code && SDAM_RECOVERING_CODES.has(err.code)) {
  257. return true;
  258. }
  259. return err.message.match(/not master or secondary/) || err.message.match(/node is recovering/);
  260. }
  261. function isNotMasterError(err) {
  262. if (err.code && SDAM_NOTMASTER_CODES.has(err.code)) {
  263. return true;
  264. }
  265. if (isRecoveringError(err)) {
  266. return false;
  267. }
  268. return err.message.match(/not master/);
  269. }
  270. function isNodeShuttingDownError(err) {
  271. return err.code && SDAM_NODE_SHUTTING_DOWN_ERROR_CODES.has(err.code);
  272. }
  273. /**
  274. * Determines whether SDAM can recover from a given error. If it cannot
  275. * then the pool will be cleared, and server state will completely reset
  276. * locally.
  277. *
  278. * @ignore
  279. * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
  280. * @param {MongoError|Error} error
  281. */
  282. function isSDAMUnrecoverableError(error) {
  283. // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
  284. // close event are considered unrecoverable
  285. if (error instanceof MongoParseError || error == null) {
  286. return true;
  287. }
  288. if (isRecoveringError(error) || isNotMasterError(error)) {
  289. return true;
  290. }
  291. return false;
  292. }
  293. module.exports = {
  294. MongoError,
  295. MongoNetworkError,
  296. MongoNetworkTimeoutError,
  297. MongoParseError,
  298. MongoTimeoutError,
  299. MongoServerSelectionError,
  300. MongoWriteConcernError,
  301. isRetryableError,
  302. isSDAMUnrecoverableError,
  303. isNodeShuttingDownError,
  304. isRetryableWriteError,
  305. isNetworkErrorBeforeHandshake
  306. };