This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

preface

Memory leakage is a serious problem, but so far there has not been a very effective detection scheme, this scheme is a targeted single point of breakthrough.

In our work, we will register event listener functions for Window, DOM node, WebSoket, or simple event center, etc., which will lead to memory leakage if added but not removed. How to warn, collect, and troubleshoot such problems?

This article is code, mainly about the use and implementation.

Event listener function memory leak, help you fix it!

Source and demo

Source: event analysis VEM

Examples abound within the project.

The core function

Our time to solve a problem is before, during, after.

We’re mostly pre and post here.

  • Warning before adding event listeners
  • The event listener function is added for statistics

Before we get to the features, let’s look at the four same features:

  1. The same event listener listener is always registered with the responding object, such as window, Socket, and Emitter.

    window.addEventListener("resize",onResize)
    
    socket.on("message", onMessage);
    
    emitter.on("message", onMessage);
    Copy the code
  2. The same event listener type is easier to understand, such as window message, resize, Audio play, etc

  3. Note that there are two types of event listener functions:

    • Function reference same
    • The functions are the same
  4. The same event listener option is optional. The EventTarget series has these options, but the other series do not. With different options, adding and removing can result in different results.

    window.addEventListener("resize",onResize)
    // Failed to remove the event listener onResize
    window.removeEventListener("resize",onResize, true)
    Copy the code

warning

Before adding the event listener function, compare the event listener function with the four same attributes, if there is duplication, alarm.

Statistics high-risk listening event functions

The core function. Count all the event information of the subordinate object of the event listener function, and output the event listener function that meets the four same attributes. If you have data coming out, chances are, you’re leaking memory.

Count all event listeners

Event statistics listen for all the event information of function dependent objects, which can be used to analyze business logic.

Take a look at how many events you’ve added. Are there any that should not exist?

The basic use

Initialization parameter

Built-in three series:

new EVM.ETargetEVM(options, et); // EventTarget series new evM. EventsEVM(options, et); // Events series new evm. CEventsEVM(options, et); / / component – emitter series

Of course, you can extend BaseEvm to create a custom series, because the above three series are also derived from BaseEvm.

The primary initialization parameter is options

  • options.isSameOptions

    Phi is a function. It is mainly used to determine the options of the event listener function.
  • options.isInWhiteList

    Phi is a function. Mainly used to determine whether to collect.
  • options.maxContentLength

    It’s a number. You can limit the length of the function you want to intercept when you’re doing statistics.

EventTarget series

  • EventTarget
  • DOM node + Windwow + document
  • XMLHttpRequest which inherits from EventTarget
  • The native WebSocket inherits from EventTarget
  • Other objects inherited from EventTarget

The basic use

<script src="Http://127.0.0.1:8080/dist/evm.js? t=5"></script>
<script>
    const evm = new EVM.ETargetEVM({
        // Whitelist, because DOM event registration is possible
        isInWhiteList(target, event, listener, options) {
            if (target === window&& event ! = ="error") {
                return true;
            }
            return false; }});// Start listening
    evm.watch();

    // Periodically print event listeners that are most likely to be registered twice
    setInterval(async function () {
        // statistics getExtremelyItems
        const data = await evm.getExtremelyItems({ containsContent: true });
        console.log("evm:", data);
    }, 3000)
</script>
Copy the code

Effect of screenshots

The screenshots are from my analysis of the actual projectOn the window objectMessage repeated addition of messagesThe number of times is up to 10

eventsA series of

  • Nodejs standard Events
  • MQTT is based on the Events library
  • socket.ioBased on theeventslibrary

The basic use

import { EventEmitter } from "events";

const evm = new win.EVM.EventsEVM(undefined, EventEmitter);
evm.watch();
setTimeout(async function () {
    // statistics getExtremelyItems
    const data = await evm.getExtremelyItems();
    console.log("evm:", data);
}, 5000)
Copy the code

Effect of screenshots

The screenshots are from my analysis of the actual project ,APP_ACT_COM_HIDE_Series events are added repeatedly

component-emitterA series of

  • component-emitter
  • Socket. IO -client (i.e. client of socket. IO)

The basic use

const Emitter = require('component-emitter');
const emitter = new Emitter();

const EVM = require('.. /.. /dist/evm');

const evm = new EVM.CEventsEVM(undefined, Emitter);
evm.watch();

// Other code

evm.getExtremelyItems()
    .then(function (res) {
        console.log("res:", res.length);
        res.forEach(r= > {
            console.log(r.type, r.constructor, r.events); })})Copy the code

Effect of screenshots

Basic idea of event analysis

The summary of the previous article:

  1. WeakRefSet up andtargetThe association of objects does not affect their collection
  2. rewriteEventTargetEventEmitterTwo series of subscribing and unsubscribing related methods that collect event registration information
  3. FinalizationRegistry listeningtargetRecycle, and clean up related data
  4. Function alignment, in addition to reference alignment, also has content alignment
  5. For functions that follow BIND, override the BIND method to retrieve the original method code content

The code structure

The basic structure of the code is as follows:

Specific notes are as follows:

evm
    CEvents.ts // Components - Emitter series, derived from BaseEvm
    ETarget.ts // EventTarget series, inherited from BaseEvm
    Events.ts  // Events series, inherited from BaseEvm
BaseEvm.ts  // Core logic class
custom.d.ts 
EventEmitter.ts // Simple event center
EventsMap.ts // The core of the data store
index.ts // Import file
types.ts // Type request
util.ts / / tools
Copy the code

The core to realize

EventsMap.ts

Responsible for data storage and basic statistics.

Data storage structure :(two-layer Map)

 Map<WeakRef<Object>, Map<EventType, EventsMapItem<T>[]>>();
 
interface EventsMapItem<O = any> {
    listener: WeakRef<Function>;
    options: O
}

Copy the code

The outline of the internal structure is as follows:

Methods are easy to understand. You may have noticed that some methods are followed by the word byTarget because they use Map storage internally, but the key type is WeakRef.

When we add or delete event listeners, the object passed in must be a normal target object. We need to go through an extra step, and check the corresponding key through target. This is what byTarget means.

Here are some of the methods used:

  • GetKeyFromTarget gets the key from the target object
  • Keys retrieves the key values of all weak references
  • AddListener Adds a listener function
  • RemoveListener Removes the listener function
  • Remove Deletes all data of a key
  • RemoveByTarget Removes all data from a key by target
  • RemoveEventsByTarget Deletes all data of an event type for a key by target
  • HasByTarget Checks whether a key exists by target
  • Does has have a key
  • GetEventsObj retrieves all event information for a target
  • HasListener Whether a target has an event listener
  • GetExtremelyItems gets high-risk event listener information
  • Get data Get data

BaseEVM

The outline of the internal structure is as follows:

The core implementation is Watch and cancel, inherit BaseEVM and rewrite these two methods, and you get a new series.

The two core methods of statistics are statistics and getExtremelyItems.

Here are some of the methods used:

  • InnerAddCallback listens for the addition of the event function and collects information about it
  • InnerRemoveCallback listens for the addition of event functions and cleans up the information
  • CheckAndProxy checks and executes the proxy
  • RestoreProperties Restores the proxied property
  • The GC performs garbage collection if it can
  • #getListenerContent Gets function content when counting
  • #getListenerInfo gets function information, mainly name and content.
  • Statistics Statistics about all event listening functions.
  • #getExtremelyListeners are collecting statistics of critical events
  • GetExtremelyItems aggregates information about high-risk events based on #getExtremelyListeners.
  • Watch performs listening and needs to be overridden
  • Cancel Cancels listening. Method that needs to be overridden
  • RemoveByTarget cleans up all data for an object
  • RemoveEventsByTarget Clears event listeners of a certain type for an object

ETargetEVM

We’ve already mentioned that there are actually three families implemented, so let’s use ETargetEVM as an example to see how we can get the collection and statistics for listening on a particular family of events through inheritance and overwriting.

  1. The core is to rewrite watch and cancel, corresponding to the agent and cancel the agent respectively
  2. checkAndProxyIs the core, which encapsulates the proxy process and filters data by customizing the second argument (function).
  3. It’s that simple
const DEFAULT_OPTIONS: BaseEvmOptions = {
    isInWhiteList: boolenFalse,
    isSameOptions: isSameETOptions
}

const ADD_PROPERTIES = ["addEventListener"];
const REMOVE_PROPERTIES = ["removeEventListener"];

/** * EVM for EventTarget */
export default class ETargetEVM extends BaseEvm<TypeListenerOptions> {

    protected orgEt: any;
    protected rpList: {
        proxy: object;
        revoke: () = > void; } [] = []; protected et: any;constructor(options: BaseEvmOptions = DEFAULT_OPTIONS, et: any = EventTarget) {
        super({... DEFAULT_OPTIONS, ... options });if (et == null| |! isObject(et.prototype)) {throw new Error("The prototype of the parameter et must be a valid object.")}this.orgEt = { ... et };this.et = et;

    }

    #getListenr(listener: Function | ListenerWrapper) {
        if (typeof listener == "function") {
            return listener
        }
        return null;
    }

    #innerAddCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) = > {
        const fn = this.#getListenr(listener)
        if(! isFunction(fnas Function)) {
            return;
        }
        return super.innerAddCallback(target, event, fn as Function, options);
    }

    #innerRemoveCallback: EVMBaseEventListener<void, string> = (target, event, listener, options) = > {
        const fn = this.#getListenr(listener)
        if(! isFunction(fnas Function)) {
            return;
        }
        return super.innerRemoveCallback(target, event, fn as Function, options);
    }


    watch() {
        super.watch();
        let rp;
        // addEventListener 
        rp = this.checkAndProxy(this.et.prototype, this.#innerAddCallback, ADD_PROPERTIES);
        if(rp ! = =null) {
            this.rpList.push(rp);
        }
        // removeEventListener
        rp = this.checkAndProxy(this.et.prototype, this.#innerRemoveCallback, REMOVE_PROPERTIES);
        if(rp ! = =null) {
            this.rpList.push(rp);
        }

        return () = > this.cancel();
    }

    cancel() {
        super.cancel();
        this.restoreProperties(this.et.prototype, this.orgEt.prototype, ADD_PROPERTIES);
        this.restoreProperties(this.et.prototype, this.orgEt.prototype, REMOVE_PROPERTIES);
        this.rpList.forEach(rp= > rp.revoke());
        this.rpList = []; }}Copy the code

conclusion

  • A set of storage structure is designed separatelyEventsMap
  • Encapsulate the underlying logic inBaseEVM
  • Some methods can be overridden by inheritance to satisfy different event monitoring scenarios.

Write in the last

Please go to the technical exchange groupCome here,. Or add my wechat Dirge-Cloud and take me to learn together.