python / python/cpython

Dropping concurrent.futures.Executor.map result cancels pending futures

Ouverte
#136,578 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

stdlib topic-multiprocessing type-bug
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

Bug report

Bug description:

Code:

from concurrent.futures import ThreadPoolExecutor

data = [
    list(range(0, 5)),
    list(range(5, 10)),
]


def _f(x):
    print(f"Processing {x}")
    return True


print("=== 1 - No consumption from the iterator ===")

executor = ThreadPoolExecutor(max_workers=1)
for ints in data:
    executor.map(_f, ints)
executor.shutdown(wait=True)

print("=== 2 - Consume all values from the iterator ===")

executor = ThreadPoolExecutor(max_workers=1)
for ints in data:
    futures = executor.map(_f, ints)
    results = list(futures)
executor.shutdown(wait=True)

print("=== 3 - Consume one value from the iterator ===")

executor = ThreadPoolExecutor(max_workers=1)
for ints in data:
    futures = executor.map(_f, ints)
    first = next(futures)
executor.shutdown(wait=True)

print("=== 4 - Dropping iterator cancels remaining futures ===")

executor = ThreadPoolExecutor(max_workers=1)
futures = executor.map(_f, range(0, 5))
first = next(futures)
del futures
executor.shutdown(wait=True)

Result:

=== 1 - No consumption from the iterator ===
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9
=== 2 - Consume all values from the iterator ===
Processing 0
Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9
=== 3 - Consume one value from the iterator ===
Processing 0
Processing 1
Processing 5
Processing 6
Processing 7
Processing 8
Processing 9
=== 4 - Dropping iterator cancels remaining futures ===
Processing 0
Processing 1

The behaviour seems to be:

  • If the iterator returned from map is never used (case 1), futures are not cancelled
  • If the iterator returned from map is exhausted (case 2), futures are not cancelled
  • If the iterator returned from map is partially consumed and then dropped (cases 3 & 4), the remaining futures are cancelled

We hit this doing a version of case 3, calling any on the iterator, which short-circuited, causing the remaining futures to not execute. This tripped us up and seems like quite a confusing behaviour that is not flagged in the docs.

It looks like this is caused by this code: https://github.com/python/cpython/blob/main/Lib/concurrent/futures/_base.py#L669-L671

Possibly related to https://github.com/python/cpython/issues/108518

CPython versions tested on:

3.12

Operating systems tested on:

Linux

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par reproduire les quatre cas sur CPython 3.12/Linux, puis examinez Lib/concurrent/futures/_base.py aux lignes 669-671 ainsi que l’issue associée #108518. Déterminez le comportement attendu pour les itérateurs map partiellement consommés, ajoutez une couverture de régression ou de la documentation selon le cas, et vérifiez que les cas d’exécution signalés sont traités de manière cohérente.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
backend
Type d'issue
Bug
Difficulté
4/5
Temps estimé
3-5 jours
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
42/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.