ready-4 IT

Pylance à la limite : maîtriser les fuites mémoire avec les opérations de fichiers haute fréquence

Ce site & mes outils – aujourd'hui : Pylance vs. ClutchPoint

Que se passe-t-il quand on développe une extension VS Code qui effectue des milliers d'opérations de fichiers par seconde — et que Pylance tente d'indexer chacune d'entre elles ? C'est exactement ce que j'ai vécu lors du développement de ClutchPoint.

L'Extension Host s'est alourdi, l'interface a commencé à ramer, et grâce à ma propre extension de diagnostic Gist Factor VS, j'ai pu isoler le coupable : ms-python.vscode-pylance.

  • Plus de 500 listeners actifs non nettoyés lors de cycles de suppression rapides
  • Blocage de la boucle d'événements par un Extension Host surchargé
  • Hausse massive de RAM — le ramasse-miettes n'arrivait plus à suivre

Je rédige ceci comme un retour d'expérience réel — configuration exacte incluse — car Pylance est inestimable dans les projets standards, mais devient un vrai piège dans des environnements inhabituels.

Le régime Pylance — 3 étapes contre les fuites mémoire Observer


TL;DR (Le « régime Pylance »)

Si vous ne voulez changer qu'une seule chose maintenant :

// settings.json
{
  "python.analysis.diagnosticMode": "openFilesOnly"
}

Cela seul arrête la consommation RAM la plus agressive. La configuration complète suit ci-dessous.


Le problème : quand le serveur de langage devient le goulot d'étranglement

Lors du développement de ClutchPoint — une extension VS Code pour les opérations de fichiers haute fréquence automatisées — j'ai constaté que VS Code se mettait à ramer sévèrement à haute fréquence de fichiers.

Grâce à mon extension de diagnostic Gist Factor VS, j'ai pu isoler rapidement le coupable : ms-python.vscode-pylance.

Le problème : Pylance tente d'indexer immédiatement chaque fichier nouvellement créé ou modifié. Dans un workflow qui génère des centaines de fichiers temporaires, cela entraîne :

  • Des fuites mémoire massives : l'utilisation RAM monte et ne redescend plus.
  • Un blocage de la boucle d'événements : Pylance monopolise l'Extension Host au point que les autres extensions — et l'interface elle-même — répondent avec un délai.
  • Des listeners qui fuient : plus de 500 listeners actifs non nettoyés mesurables lors de cycles de suppression rapides.

L'analyse : pourquoi Pylance « fuit »

Pylance est basé sur Pyright. Dans les projets standards, son comportement — indexation profonde de tous les fichiers du workspace — est intentionnel et utile. Dans un environnement haute fréquence ou avec de grands workspaces (par ex. dans un setup Vagrant), cet « zèle » provoque :

  1. Le ramasse-miettes qui n'arrive plus à nettoyer.
  2. Chaque nouvel événement de fichier qui déclenche une nouvelle tâche d'analyse — la file grandit en continu.
  3. Des listeners d'événements de fichiers enregistrés mais jamais proprement désenregistrés.

Le résultat est une fuite d'observateurs classique au niveau de l'Extension Host.


La solution : le « régime Pylance »

Pour rétablir la stabilité sans perdre l'intelligence de Pylance, nous avons mis en place une stratégie en trois étapes :

1) Optimiser la configuration (settings.json)

L'étape la plus importante est de freiner l'ardeur de Pylance :

{
  "python.analysis.diagnosticMode": "openFilesOnly",
  "python.analysis.indexing": false,
  "python.analysis.packageIndexDepths": [
    { "name": "", "depth": 1 }
  ],
  "python.analysis.typeCheckingMode": "basic",
  "python.analysis.persistAllIndices": false
}

Remarque : Ces paramètres sont intentionnellement restrictifs. Pour les équipes avec de grands monorepos Python, ils peuvent légèrement réduire le confort. En compromis, appliquez-les dans les Workspace Settings plutôt que les User Settings — ainsi le régime ne s'applique qu'aux projets sollicités.

Colonnes
"python.analysis.diagnosticMode"
"openFilesOnly"
Seuls les fichiers ouverts sont analysés. Le levier le plus efficace contre les fuites RAM.
"python.analysis.indexing"
false
Arrête l'indexation globale en arrière-plan de tout le workspace.
"python.analysis.packageIndexDepths"
[{"name": "", "depth": 1}]
Limite la profondeur à laquelle Pylance plonge dans les bibliothèques installées.
"python.analysis.typeCheckingMode"
"basic"
Réduit la charge de calcul sans sacrifier la vérification de types fondamentale.
"python.analysis.persistAllIndices"
false
Réduit les I/O disque en écrivant moins de fichiers de cache.

2) Ajustement architectural : throttling dans ClutchPoint

Dans la logique de l'extension ClutchPoint, nous avons introduit du batching. Au lieu de bombarder Pylance avec chaque événement de fichier individuel, les opérations sont groupées et poussées ensemble. Cela donne au serveur de langage le temps de « respirer » :

// Simplifié — batching au lieu de déclencher sur chaque sauvegarde individuelle
const pendingFiles = new Set<string>();
let batchTimer: NodeJS.Timeout | undefined;

function scheduleAnalysis(uri: vscode.Uri) {
  pendingFiles.add(uri.fsPath);
  if (batchTimer) clearTimeout(batchTimer);
  batchTimer = setTimeout(() => {
    // Notifier Pylance une seule fois par batch
    pendingFiles.clear();
    batchTimer = undefined;
  }, 500); // 500 ms de debounce
}

3) Transparence pour l'utilisateur : Gist Factor VS

Grâce à Gist Factor VS, nous donnons maintenant un retour à l'utilisateur : quand la latence système augmente à cause de Pylance, une notification apparaît dans l'éditeur. Cela rend clair que ce n'est pas l'application de l'utilisateur mais le serveur de langage qui est à la limite — un « arbitre neutre » entre l'extension et l'éditeur.


Bonus : ce que Gist Factor VS a également trouvé — LEAK_WEBVIEW_NO_DISPOSED_CHECK

Pendant l'analyse des fuites Pylance, Gist Factor VS a simultanément découvert un second type d'erreur très répandu dans les extensions VS Code : LEAK_WEBVIEW_NO_DISPOSED_CHECK.

Voici un extrait représentatif de la sortie de diagnostic réelle (condensé ; original : 35 occurrences dans 8 fichiers) :

[
  {
    "code": "LEAK_WEBVIEW_NO_DISPOSED_CHECK",
    "severity": 4,
    "message": "webview.postMessage called without a check for a disposed panel.",
    "source": "gistfactor",
    "startLineNumber": 209
  },
  {
    "code": "LEAK_WEBVIEW_NO_DISPOSED_CHECK",
    "severity": 4,
    "message": "webview.postMessage called without a check for a disposed panel.",
    "source": "gistfactor",
    "startLineNumber": 175
  }
]

Qu'est-ce que cela signifie ?

Le pattern est simple mais dangereux : une extension envoie des messages à un panneau webview — sans vérifier si le panneau est encore ouvert.

  1. L'utilisateur ferme un onglet dashboard → le panneau est disposed.
  2. Un processus asynchrone tourne encore et veut envoyer un résultat.
  3. webview.postMessage est appelé → erreur ou référence zombie en mémoire.

Dans un scénario haute fréquence (ClutchPoint déclenche constamment des événements de fichiers qui veulent mettre à jour les dashboards), cela s'accumule en un vrai problème de fuite.

Le correctif : SafeMessenger

Au lieu de parsemer le code de gardes if (this.panel), un wrapper central résout le problème une fois pour toutes :

// src/runtime/safeMessenger.ts
import * as vscode from 'vscode';

export class SafeMessenger {
    private _isDisposed = false;

    constructor(private readonly _panel: vscode.WebviewPanel) {
        this._panel.onDidDispose(() => {
            this._isDisposed = true;
        });
    }

    public postMessage(message: unknown): void {
        if (this._isDisposed) {
            return; // Panneau déjà fermé — message ignoré
        }
        try {
            this._panel.webview.postMessage(message);
        } catch (err) {
            console.error('[SafeMessenger] Failed to send message:', err);
        }
    }

    public get isDisposed(): boolean {
        return this._isDisposed;
    }
}

Avant (risqué) :

this.panel.webview.postMessage({ command: 'update', data: payload }); // ❌

Après (conforme Gist Factor) :

this.messenger.postMessage({ command: 'update', data: payload }); // ✅

Ce wrapper est aussi le fondement du Disturber Radar dans Gist Factor VS : les tentatives d'envoi échouées peuvent être loguées et affichées dans le dashboard — offrant une visibilité claire sur quelles extensions tentent activement d'atteindre des panneaux déjà fermés.


Alternatives : quand le régime ne suffit pas

Si la configuration seule ne suffit pas, deux alternatives viables existent :

Ruff (pour décharger Pylance)

Ruff est un outil de linting et formatting écrit en Rust — extrêmement rapide et économe en ressources. Il peut remplacer complètement Pylance pour le linting, laissant le serveur de langage se concentrer uniquement sur la vérification de types.

Condition préalable : l'extension VS Code charliermarsh.ruff doit être installée. Il est recommandé de placer cette entrée dans les paramètres utilisateur de VS Code (%APPDATA%\Code\User\settings.json) :

{
  "ruff.nativeServer": "on",
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff"
  }
}

La simple affectation editor.defaultFormatter = charliermarsh.ruff ne prend pas effet si l'extension est absente.

Ruff ne remplace pas Pylance — répartition des tâches en un coup d'œil :

Fonction Pylance Ruff
IntelliSense / Autocomplete
Vérification de types (via Pyright)
Go-to-Definition
Hover docs / signatures
Linting (flake8, pylint…) faible
Formatting (compatible Black)
Tri des imports (isort)
Vitesse normale 10–100× plus rapide

Pylance et Ruff — aperçu des outils complémentaires

Ruff peut-il remplacer Pylance ? Seulement partiellement. Ruff prend en charge le linting et le formatting entièrement — mais pas l'IntelliSense, la vérification de types ni Go-to-Definition. Quiconque utilise ces fonctionnalités IDE a toujours besoin de Pylance.

Pour aller plus loin : Pylance ou Ruff ? Comprendre les outils, bien les combiner

Pour les projets de scripts et CLI purs (validateurs de manifeste, outils d'automatisation, scripts qui tournent principalement en terminal) : Pylance peut être désactivé par projet sans affecter les autres :

// .vscode/settings.json dans le dossier du projet
{
  "python.languageServer": "None"
}

Ruff gère alors le linting et le formatting — l'Extension Host reste complètement libre, sans impacter les autres projets.

BasedPyright (en remplacement de Pylance)

BasedPyright est un fork communautaire du cœur Pyright — plus efficace en ressources que Pylance et sans télémétrie Microsoft. Une option sérieuse pour les projets qui souffrent continuellement de Pylance.


Conclusion

Pylance est puissant — mais n'a pas été conçu pour tous les environnements. Pour les développeurs travaillant avec des opérations de fichiers automatisées, de grands workspaces Vagrant ou des monorepos, un « régime » de paramètres est souvent inévitable pour garder l'Extension Host stable.

La première étape décisive :

"python.analysis.diagnosticMode": "openFilesOnly"

Observez ensuite l'utilisation RAM de pylance dans le Gestionnaire des tâches — la différence est généralement visible immédiatement.

Contexte : environnement de test - OS : Windows 10 Home / Windows 11 - VS Code : 1.80+ - Pylance : ms-python.vscode-pylance - Workspace Vagrant avec plusieurs projets PHP/JS/Python ouverts - Extension : ClutchPoint (High-Frequency File Operations) - Diagnostic : Gist Factor VS

Support the Journey & Development! 🚀

If my IT guides or the Snapmaker Wiki saved your project (or your hardware), I'd appreciate a coffee! ☕
Your support doesn't just cover hosting and testing costs—it also fuels the development of my apps and tools. Every donation helps me dedicate more time to coding solutions that make our tech-life easier. Thank you for being part of this!

☕ Donate via PayPal