Skip to content

Commit

Permalink
Add remote API to uninstall the global error handler in RN
Browse files Browse the repository at this point in the history
Reviewed By: fromcelticpark

Differential Revision: D6426209

fbshipit-source-id: 804e73e0dc4e4b85b336e3627c00840d2ff3c9d6
  • Loading branch information
pakoito authored and facebook-github-bot committed Dec 11, 2017
1 parent ed2bfcb commit 1d16923
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 11 deletions.
16 changes: 15 additions & 1 deletion Libraries/BatchedBridge/BatchedBridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@
'use strict';

const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue();

// MessageQueue can install a global handler to catch all exceptions where JS users can register their own behavior
// This handler makes all exceptions to be handled inside MessageQueue rather than by the VM at its origin
// This makes stacktraces to be placed at MessageQueue rather than at where they were launched
// The parameter __fbUninstallRNGlobalErrorHandler is passed to MessageQueue to prevent the handler from being installed
//
// __fbUninstallRNGlobalErrorHandler is conditionally set by the Inspector while the VM is paused for intialization
// If the Inspector isn't present it defaults to undefined and the global error handler is installed
// The Inspector can still call MessageQueue#uninstallGlobalErrorHandler to uninstalled on attach

const BatchedBridge = new MessageQueue(
// $FlowFixMe
typeof __fbUninstallRNGlobalErrorHandler !== 'undefined' &&
__fbUninstallRNGlobalErrorHandler === true, // eslint-disable-line no-undef
);

// Wire up the batched bridge on the global object so that we can call into it.
// Ideally, this would be the inverse relationship. I.e. the native environment
Expand Down
26 changes: 24 additions & 2 deletions Libraries/BatchedBridge/MessageQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,21 @@ class MessageQueue {

__spy: ?(data: SpyData) => void;

constructor() {
__guard: (() => void) => void;

constructor(shouldUninstallGlobalErrorHandler: boolean = false) {
this._lazyCallableModules = {};
this._queue = [[], [], [], 0];
this._successCallbacks = [];
this._failureCallbacks = [];
this._callID = 0;
this._lastFlush = 0;
this._eventLoopStartTime = new Date().getTime();
if (shouldUninstallGlobalErrorHandler) {
this.uninstallGlobalErrorHandler();
} else {
this.installGlobalErrorHandler();
}

if (__DEV__) {
this._debugInfo = {};
Expand Down Expand Up @@ -252,11 +259,26 @@ class MessageQueue {
}
}

uninstallGlobalErrorHandler() {
this.__guard = this.__guardUnsafe;
}

installGlobalErrorHandler() {
this.__guard = this.__guardSafe;
}

/**
* Private methods
*/

__guard(fn: () => void) {
// Lets exceptions propagate to be handled by the VM at the origin
__guardUnsafe(fn: () => void) {
this._inCall++;
fn();
this._inCall--;
}

__guardSafe(fn: () => void) {
this._inCall++;
try {
fn();
Expand Down
87 changes: 79 additions & 8 deletions Libraries/BatchedBridge/__tests__/MessageQueue-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails oncall+react_native
* @format
*/
'use strict';

Expand Down Expand Up @@ -38,10 +39,12 @@ describe('MessageQueue', function() {
queue = new MessageQueue();
queue.registerCallableModule(
'MessageQueueTestModule',
MessageQueueTestModule
MessageQueueTestModule,
);
queue.createDebugLookup(0, 'MessageQueueTestModule',
['testHook1', 'testHook2']);
queue.createDebugLookup(0, 'MessageQueueTestModule', [
'testHook1',
'testHook2',
]);
});

it('should enqueue native calls', () => {
Expand All @@ -65,7 +68,15 @@ describe('MessageQueue', function() {

it('should call the stored callback', () => {
let done = false;
queue.enqueueNativeCall(0, 1, [], () => {}, () => { done = true; });
queue.enqueueNativeCall(
0,
1,
[],
() => {},
() => {
done = true;
},
);
queue.__invokeCallback(1, []);
expect(done).toEqual(true);
});
Expand All @@ -83,32 +94,92 @@ describe('MessageQueue', function() {
});

it('should throw when calling with unknown module', () => {
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
const unknownModule = 'UnknownModule',
unknownMethod = 'UnknownMethod';
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
);
});

it('should return lazily registered module', () => {
const dummyModule = {}, name = 'modulesName';
const dummyModule = {},
name = 'modulesName';
queue.registerLazyCallableModule(name, () => dummyModule);

expect(queue.getCallableModule(name)).toEqual(dummyModule);
});

it('should not initialize lazily registered module before it was used for the first time', () => {
const dummyModule = {}, name = 'modulesName';
const dummyModule = {},
name = 'modulesName';
const factory = jest.fn(() => dummyModule);
queue.registerLazyCallableModule(name, factory);
expect(factory).not.toHaveBeenCalled();
});

it('should initialize lazily registered module only once', () => {
const dummyModule = {}, name = 'modulesName';
const dummyModule = {},
name = 'modulesName';
const factory = jest.fn(() => dummyModule);
queue.registerLazyCallableModule(name, factory);
queue.getCallableModule(name);
queue.getCallableModule(name);
expect(factory).toHaveBeenCalledTimes(1);
});

it('should catch all exceptions if the global error handler is installed', () => {
const errorMessage = 'intentional error';
const errorModule = {
explode: function() {
throw new Error(errorMessage);
},
};
const name = 'errorModuleName';
const factory = jest.fn(() => errorModule);
queue.__guardSafe = jest.fn(() => {});
queue.__guardUnsafe = jest.fn(() => {});
queue.installGlobalErrorHandler();
queue.registerLazyCallableModule(name, factory);
queue.callFunctionReturnFlushedQueue(name, 'explode', []);
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
});

it('should propagate exceptions if the global error handler is uninstalled', () => {
queue.uninstallGlobalErrorHandler();
const errorMessage = 'intentional error';
const errorModule = {
explode: function() {
throw new Error(errorMessage);
},
};
const name = 'errorModuleName';
const factory = jest.fn(() => errorModule);
queue.__guardUnsafe = jest.fn(() => {});
queue.__guardSafe = jest.fn(() => {});
queue.registerLazyCallableModule(name, factory);
queue.uninstallGlobalErrorHandler();
queue.callFunctionReturnFlushedQueue(name, 'explode');
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(2);
expect(queue.__guardSafe).toHaveBeenCalledTimes(0);
});

it('should catch all exceptions if the global error handler is re-installed', () => {
const errorMessage = 'intentional error';
const errorModule = {
explode: function() {
throw new Error(errorMessage);
},
};
const name = 'errorModuleName';
const factory = jest.fn(() => errorModule);
queue.__guardUnsafe = jest.fn(() => {});
queue.__guardSafe = jest.fn(() => {});
queue.registerLazyCallableModule(name, factory);
queue.uninstallGlobalErrorHandler();
queue.installGlobalErrorHandler();
queue.callFunctionReturnFlushedQueue(name, 'explode');
expect(queue.__guardUnsafe).toHaveBeenCalledTimes(0);
expect(queue.__guardSafe).toHaveBeenCalledTimes(2);
});
});

0 comments on commit 1d16923

Please sign in to comment.