Geek++ · Dossier 04 / 25

File d’upload, Queue Rank, slots et ordonnancement

Étude technique publique · 2,178 mots · ≈ 10 min de lecture attentive

Étude intégraleSans réflexion CODEXSources & constantes conservées
File d’upload
SLOT
01 · Alice
02 · Bob
74 · Akshaan
75 · Claire

0. Résumé exécutif

Le protocole impose principalement

OP_STARTUPLOADREQ
OP_ACCEPTUPLOADREQ
OP_OUTOFPARTREQS
OP_REQUESTPARTS
OP_SENDINGPART
OP_QUEUERANK / OP_QUEUERANKING

Le client décide localement

comment calculer le score
comment classer la file
combien de slots ouvrir
quand faire tourner un slot
quelle priorité donner aux fichiers
quelle taille maximale donner à la file
comment distribuer la bande passante entre les slots

La file d’upload est donc un excellent terrain de modernisation sans modification du wire protocol.

  1. Queue Observatory : rendre mesurable et explicable chaque décision d’ordonnancement ;
  2. corriger le vieux problème “priorité du fichier utilisé pour le score ≠ fichier finalement demandé”, explicitement marqué TODO depuis au moins eMule 0.50a et toujours présent dans 0.72a ;
  3. auditer l’algorithme d’ouverture et d’alimentation des slots avec des mesures modernes, plutôt que modifier arbitrairement les constantes.

1. Racine protocolaire

edonkey-protocol.txt documente déjà les opérations :

0x54 Slot request
0x55 Slot given
0x56 Slot release
0x57 Slot taken

0x47 Request parts
0x46 Sending part
0x49 End of download

Dans eMule 0.72a, Opcodes.h conserve notamment :

OP_REQUESTPARTS       0x47
OP_STARTUPLOADREQ     0x54
OP_ACCEPTUPLOADREQ    0x55
OP_OUTOFPARTREQS      0x57
OP_QUEUERANK          0x5C
OP_QUEUERANKING       0x60

La file d’attente elle-même et le score ne sont donc pas imposés par ces paquets.


2. Entrée dans la file

2.1 Réception de la demande

ListenSocket.cpp:465-480

À la réception de :

OP_STARTUPLOADREQ

eMule :

  1. vérifie que le handshake est terminé ;
  2. cherche le fichier partagé correspondant au hash ;
  3. enregistre ce fichier comme fichier demandé ;
  4. appelle :
theApp.uploadqueue->AddClientToQueue(client);

3. AddClientToQueue : beaucoup plus qu’un simple push_back

UploadQueue.cpp:501-654

Cette fonction applique déjà une politique importante.

3.1 Limitation liée aux callbacks LowID

Le bloc 514-523 refuse certains clients quand :

  • notre eMule est lui-même firewalled ;
  • le demandeur n’a pas de port Kad ;
  • il n’est ni une source actuelle ni un ami ;
  • il n’est pas sur notre serveur ;
  • plus de 50 clients sont déjà en attente.

Le commentaire précise que le but est de limiter l’abus des callbacks LowID.

3.2 Bannis / fréquence de requêtes

Le client :

IncrementAskedCount();
SetLastUpRequest();
AddRequestCount(...)

puis les clients bannis sont rejetés.

Le mécanisme anti-request abuse est donc déjà lié à l’entrée en queue.

3.3 Collision / doublon de UserHash

UploadQueue.cpp:533-580

Si un client existe déjà :

  • SecureIdent est utilisé pour départager les entrées ;
  • un client identifié peut être conservé face à un doublon non identifié ;
  • si aucun des deux n’est identifiable, les entrées ambiguës peuvent être rejetées.

Lien direct avec le dossier 03.

3.4 Limite par IP

UploadQueue.cpp:581-595

La file évite normalement plus de trois clients depuis une même IP / liste de clients suivis.

Il s’agit clairement d’une politique anti-abus locale.


4. File souple et file dure

UploadQueue.cpp:617-633

softQueueLimit = thePrefs.GetQueueSize();

hardQueueLimit =
    softQueueLimit
    + max(softQueueLimit, 800) / 4;

Avec la valeur par défaut historique :

soft = 5000
hard = 6250

Entre soft et hard

Un client standard n’est encore accepté que si :

GetCombinedFilePrioAndCredit()
    >= moyenne de la file

ou s’il dispose d’un Friend Slot.

Cette fonction combine :

10 * priorité fichier * crédit

Référence :

UploadClient.cpp:137-145

5. Cas spéciaux contournant la file

5.1 Collection Priority Slot

UploadQueue.cpp:605-613

Une petite collection eMule :

extension collection
taille < 50 KiB

peut contourner la file et obtenir un slot direct si le client est connecté.

Constante :

Opcodes.h:129

MAXPRIORITYCOLL_SIZE = 50 * 1024

5.2 Friend Slot

Dans GetScore() :

if (IsFriend() && GetFriendSlot() && !HasLowID())
    return 0x0FFFFFFF;

Il reçoit donc un score pratiquement imbattable.


6. Le score d’attente eMule 0.72a

UploadClient.cpp:180-229

Conditions préliminaires :

  • username présent ;
  • crédits disponibles ;
  • fichier demandé encore partagé ;
  • client non IS_IDBADGUY ;
  • client non banni ;
  • LowID non connecté exclu lorsque sysvalue=true.

Formule normale

dwBaseValue =
    GetTickCount() - GetWaitStartTime();

score_seconds =
    dwBaseValue / 1000;

if (CreditSystem)
    score *= CreditRatio;

score *= FilePriority / 10;

Résumé :

score =
temps d’attente en secondes
× crédit
× coefficient fichier

9. Un TODO historique très intéressant

Dans eMule 0.50a :

UploadClient.cpp:161-164

et encore dans eMule 0.72a :

UploadClient.cpp:156-159

le même commentaire apparaît :

un client peut demander deux fichiers ; il peut être classé grâce à la priorité élevée d’un fichier puis demander finalement quelque chose de complètement différent.

C’est donc un problème connu depuis longtemps et resté explicitement non résolu.

Pourquoi c’est important

Le score est calculé à partir de :

requpfileid

au moment de l’évaluation.

Mais une fois le slot attribué, les demandes de blocs peuvent amener le client à utiliser un autre fichier.

UploadQueue.cpp:324-343 vérifie même le FileID du premier bloc demandé et, s’il correspond à un autre fichier partagé, fait :

cur_client->SetUploadFileID(pCurrentUploadFile);

Le changement de fichier pendant une session normale est donc prévu.

Conséquence possible

Un client peut, dans certaines séquences valides :

attendre/classer avec fichier A très prioritaire
        ↓
obtenir le slot
        ↓
demander des blocs du fichier B moins prioritaire

Cela ne prouve pas une exploitation volontaire.

Mais cela signifie que la politique de priorité n’est pas strictement attachée à la ressource effectivement servie.


11. Classement et Queue Rank

11.1 Sélection du meilleur client

UploadQueue.cpp:106-153

FindBestClientInQueue() parcourt la file et choisit le score maximal.

Les LowID non connectés ne peuvent pas recevoir immédiatement le slot.

Cependant, si le meilleur score appartient à un LowID non connecté :

m_bAddNextConnect = true;

Il sera privilégié lorsqu’il reviendra.

11.2 Calcul du QR

UploadQueue.cpp:830-840

rank = 1;

for each other client:
    if other.score > myscore:
        rank++;

Nuance :

le QR correspond au nombre de clients ayant un score strictement supérieur, +1.

Deux clients de score exactement identique peuvent donc théoriquement recevoir le même rank.

Ce n’est pas nécessairement l’index physique de la liste.

11.3 Envoi

UploadClient.cpp:558-572

OP_QUEUERANKING

Paquet de 12 octets :

  • rang sur 16 bits ;
  • 10 octets à zéro.

aMule contient même un commentaire demandant à quoi servent ces dix octets, puis conclut qu’ils sont gardés car eMule procède ainsi.

Encore un bon exemple de compatibilité historique.


12. Retour en queue en fin de session

UploadClient.cpp:484-499

eMule envoie :

OP_OUTOFPARTREQS

avant de replacer le client dans la file.

Le commentaire explique pourquoi :

sans ce message, le downloader pourrait croire que ses anciennes requêtes de blocs sont toujours valables alors que l’uploader les a oubliées, provoquant des timeouts.

Donc ce paquet n’est pas un simple « slot terminé ».

Il sert aussi à resynchroniser l’état des deux clients.


13. Politique de session : Full Chunk

Constantes :

Opcodes.h

PARTSIZE        = 9 728 000
SESSIONMAXTRANS = PARTSIZE + 20*1024

L’option :

FullChunkTransfers

est chargée par défaut à true dans Preferences.cpp:2164.

eMule 0.72a

UploadQueue.cpp:787-793

En mode full chunk :

si le payload dépasse SESSIONMAXTRANS, le slot peut tourner si la bande passante ne peut pas simplement satisfaire un client supplémentaire.

L’idée est donc :

≈ une partie complète par session

sans forcer inutilement la rotation si la capacité permet de garder le client et d’en ouvrir un autre.


14. Politique de session sans Full Chunk

Opcodes.h

SESSIONMAXTIME = 1 heure

UploadQueue.cpp:794-815

Deux mécanismes :

  1. session > 1 h ;
  2. score actuel inférieur au meilleur score de la file.

Une protection de 6 secondes évite d’éjecter trop de slots successivement uniquement à cause du recalcul des scores.


15. Le score pendant un upload

GetScore(sysvalue=true, isdownloading=true)

Pour éviter les sessions de quelques secondes, le score d’un client en cours d’upload reçoit une logique particulière.

Le commentaire dit :

les 15 premières minutes d'upload
comptent avec un bonus destiné à empêcher
des uploads de 20 secondes

Puis le score cesse d’augmenter indéfiniment.

Cette logique est déjà présente dans eMule 0.50a.

16. Purge de la file

Opcodes.h

MAX_PURGEQUEUETIME = 1 heure

FindBestClientInQueue()

Un client est retiré si :

dernière requête + 1h dépassée
ou
fichier plus partagé

La valeur existait déjà en 0.50a.

C’est une convention de client, pas une structure du protocole.


17. Ouverture dynamique des slots en 0.72a

C’est l’une des parties où l’évolution par rapport à 0.50a est visible.

Bornes

Opcodes.h

MIN_UP_CLIENTS_ALLOWED = 2
MAX_UP_CLIENTS_ALLOWED = 100

Mais AcceptNewClient() autorise en pratique directement jusqu’à :

max(MIN_UP_CLIENTS_ALLOWED, 4)

donc quatre slots dans cette première condition.

Débit cible

UploadQueue.cpp:397-409

<= 3 slots : cible 3 KiB/s
>= 4 slots : cible = nombre de slots KiB/s
             plafonnée à 25 KiB/s

Le test « minimum » utilise 75 % de cette cible.

Décision

ForceNewClient() tient compte :

  • du débit courant ;
  • de la limite d’upload ;
  • d’UploadSpeedSense ;
  • de la cible par client ;
  • du nombre de slots réellement pleinement actifs demandé par le throttler.

La file et le throttler fonctionnent donc en boucle de rétroaction.


18. Évolution depuis eMule 0.50a

0.50a

Constantes :

UPLOAD_CLIENT_DATARATE = 3072 bytes/s
MIN_UP_CLIENTS_ALLOWED = 2
MAX_UP_CLIENTS_ALLOWED = 100

L’ouverture des slots utilise largement cette cible fixe d’environ 3 KiB/s, avec une limite interne dynamique autour de 7,5 KiB/s/client dans une autre partie de ForceNewClient().

0.72a

La logique GetTargetClientDataRate() devient variable et peut viser jusqu’à :

25 KiB/s par client

Le changelog inclus indique au 1er mars 2010 :

upload code tweaked to significantly increase
possible upload speed per slot

et le maximum de slots est ramené à 100.

Conclusion

L’algorithme de slots a déjà été modernisé au fil de l’histoire.

Ce n’est donc clairement pas une règle figée du protocole eD2K.


19. UploadBandwidthThrottler

UploadBandwidthThrottler.cpp

La distribution se fait en plusieurs phases.

19.1 Contrôle d’abord

Les paquets de contrôle en attente sont servis avant les données fichier.

19.2 « Trickle »

Les sockets qui n’ont pas reçu de données depuis environ une seconde peuvent recevoir un petit envoi afin de ne pas rester affamées.

19.3 Part égale

Commentaire explicite :

// Equal bandwidth for all slots

Le throttler donne une part aux slots jusqu’à un nombre calculé à partir du débit permis et de la cible.

19.4 Bande passante restante

Commentaire :

// Any remaining bandwidth will be used - from first to last.

La capacité restante est ensuite proposée dans l’ordre de la liste.

19.5 Feedback vers la file

Si le throttler n’arrive pas à consommer tout le budget alors qu’il semble avoir besoin de davantage de sockets, il peut fixer :

m_highestNumberOfFullyActivatedSlots =
    GetStandardListSize() + 1;

La file peut alors ouvrir un nouveau slot.


24. Comparaison aMule : preuve de liberté locale

L’archive aMule fournie suit le même protocole mais possède une politique sensiblement différente.

Score

UploadClient.cpp:75-145

Même architecture :

attente × crédits × priorité

mais avec notamment une priorité supplémentaire PR_POWERSHARE.

Queue

aMule trie réellement la waiting list et mémorise un rang pour les clients.

Il réordonne périodiquement la file.

Limite

Dans cette implémentation, l’entrée est refusée directement une fois :

m_waitinglist.size() >= GetQueueSize()

sans la zone soft/hard particulière de l’eMule 0.72a.

Slots

La politique de calcul du maximum de slots diffère également.

Sessions

Le code observé applique :

1 heure
ou
10 MiB environ

pour les slots ordinaires.

Conclusion

Même philosophie globale, mais politiques locales clairement différentes.

C’est une preuve supplémentaire que l’ordonnanceur peut évoluer sans changer eD2K.


25. Vieilles règles qui méritent un audit, pas une suppression

Liste issue du code :

MAX_PURGEQUEUETIME        1 h
SESSIONMAXTIME            1 h
SESSIONMAXTRANS           1 part + 20 KiB
queue default             ~5000
hard queue default        ~6250
MAX_UP_CLIENTS_ALLOWED    100
same IP                   ~3 entrées
old eMule version penalty ×0.5
friend slot               score maximal
collection <50 KiB        bypass

Pour chacune :

  1. comprendre l’objectif historique ;
  2. mesurer sa fréquence d’activation en 2026 ;
  3. identifier les effets secondaires ;
  4. seulement ensuite décider.

26. Une règle historique probablement devenue presque invisible

UploadClient.cpp:227-228

if ((IsEmuleClient() || GetClientSoft() < 10)
    && m_byEmuleVersion <= 0x19)
{
    fBaseValue *= 0.5f;
}

Certains très anciens clients eMule reçoivent donc un coefficient 0.5.

Cette règle existe encore dans 0.72a.

27. Queue Rank : amélioration d’interface possible

Aujourd’hui, le QR envoyé au pair est uniquement un nombre.

Client X
QR local       : 37
Attente        : 42 min
Crédit         : ×2.8
Priorité       : High ×0.9
Score          : 6350
Joignable      : oui

Pour notre propre download, on ne connaît pas la formule exacte utilisée par le pair distant.

Donc l’interface doit bien distinguer :

QR distant reçu

de :

score de notre propre file d’upload

Ne jamais prétendre expliquer le score d’un client distant si nous ne possédons que son QR.


33. Conclusion

Le protocole eD2K fournit un vocabulaire très simple :

je veux un slot
tu as un slot
donne-moi ces blocs
voici les données
retourne en attente

Tout le reste est une politique locale construite au fil des années :

score
crédits
priorités
slots
rotation
queue soft/hard
LowID handling
bandwidth scheduling

Cette politique a déjà évolué entre eMule 0.50a, 0.72a et aMule.

instrumenter l’ordonnanceur actuel, comprendre ses décisions sur le réseau réel, puis moderniser les heuristiques sans modifier les messages eD2K.

Le vieux TODO concernant le fichier réellement servi après attribution d’un slot est, à ce stade, la piste fonctionnelle la plus intéressante de ce dossier.



Dossier 04 terminé

File d’upload, Queue Rank, slots et ordonnancement

← Dossier 03Dossier 05 →