Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 298 – Die gesperrte Puffer-Schnittstelle

Autor:
Thomas Heller <theller at python.net>
Status:
Zurückgezogen
Typ:
Standards Track
Erstellt:
26. Juli 2002
Python-Version:
2.3
Post-History:
30. Juli 2002, 1. Aug. 2002

Inhaltsverzeichnis

Zusammenfassung

Diese PEP schlägt eine Erweiterung der Puffer-Schnittstelle vor, die als „gesperrte Puffer-Schnittstelle“ bezeichnet wird.

Die gesperrte Puffer-Schnittstelle vermeidet die Mängel der „alten“ Puffer-Schnittstelle [1], wie sie in Python-Versionen bis einschließlich 2.2 definiert ist, und hat die folgenden Semantiken:

  • Die Lebensdauer des abgerufenen Zeigers ist klar definiert und wird vom Client gesteuert.
  • Die Puffergröße wird als Datentyp `size_t` zurückgegeben, was den Zugriff auf große Puffer auf Plattformen ermöglicht, auf denen sizeof(int) != sizeof(void *) gilt.

(Guido merkt an: Dies klingt wie eine Änderung, die wir auch an der „alten“ Puffer-Schnittstelle vornehmen könnten, wenn wir ein weiteres Flag-Bit einführen, das *nicht* Teil der Standard-Flags ist.)

Spezifikation

Die gesperrte Puffer-Schnittstelle stellt neue Funktionen bereit, die die Größe und den Zeiger auf den internen Speicherblock jedes Python-Objekts zurückgeben, das diese Schnittstelle implementieren möchte.

Das Abrufen eines Puffers von einem Objekt versetzt dieses Objekt in einen gesperrten Zustand, währenddessen der Puffer nicht freigegeben, verkleinert oder neu zugewiesen werden darf.

Das Objekt muss durch Freigabe des Puffers wieder entsperrt werden, wenn es nicht mehr vom Client verwendet wird, indem eine andere Funktion in der gesperrten Puffer-Schnittstelle aufgerufen wird. Wenn das Objekt den Puffer während seiner Lebensdauer niemals verkleinert oder neu zuweist, kann diese Funktion NULL sein. Das Versäumnis, diese Funktion aufzurufen (wenn sie != NULL ist), ist ein Programmierfehler und kann unerwartete Ergebnisse haben.

Die gesperrte Puffer-Schnittstelle verzichtet auf das Speichersegmentmodell, das in der alten Puffer-Schnittstelle vorhanden ist – es kann nur ein einzelner Speicherblock exponiert werden.

Auf die Speicherblöcke kann zugegriffen werden, ohne die globale Interpreter-Sperre zu halten.

Implementierung

Definieren Sie ein neues Flag in `Include/object.h`

/* PyBufferProcs contains bf_acquirelockedreadbuffer,
   bf_acquirelockedwritebuffer, and bf_releaselockedbuffer */
#define Py_TPFLAGS_HAVE_LOCKEDBUFFER (1L<<15)

Dieses Flag wäre in `Py_TPFLAGS_DEFAULT` enthalten.

#define Py_TPFLAGS_DEFAULT  ( \
                    ....
                    Py_TPFLAGS_HAVE_LOCKEDBUFFER | \
                    ....
                    0)

Erweitern Sie die Struktur `PyBufferProcs` um neue Felder in `Include/object.h`.

typedef size_t (*acquirelockedreadbufferproc)(PyObject *,
                                              const void **);
typedef size_t (*acquirelockedwritebufferproc)(PyObject *,
                                               void **);
typedef void (*releaselockedbufferproc)(PyObject *);

typedef struct {
    getreadbufferproc bf_getreadbuffer;
    getwritebufferproc bf_getwritebuffer;
    getsegcountproc bf_getsegcount;
    getcharbufferproc bf_getcharbuffer;
    /* locked buffer interface functions */
    acquirelockedreadbufferproc bf_acquirelockedreadbuffer;
    acquirelockedwritebufferproc bf_acquirelockedwritebuffer;
    releaselockedbufferproc bf_releaselockedbuffer;
} PyBufferProcs;

Die neuen Felder sind vorhanden, wenn das Flag `Py_TPFLAGS_HAVE_LOCKEDBUFFER` im Typ des Objekts gesetzt ist.

Das Flag `Py_TPFLAGS_HAVE_LOCKEDBUFFER` impliziert das Flag `Py_TPFLAGS_HAVE_GETCHARBUFFER`.

Die Funktionen `acquirelockedreadbufferproc` und `acquirelockedwritebufferproc` geben im Erfolgsfall die Größe des Speicherblocks in Bytes zurück und füllen den übergebenen `void *`-Zeiger im Erfolgsfall. Wenn diese Funktionen fehlschlagen – sei es aufgrund eines Fehlers oder weil kein Speicherblock exponiert wird –, müssen sie den `void *`-Zeiger auf NULL setzen und eine Ausnahme auslösen. Der Rückgabewert ist in diesen Fällen undefiniert und sollte nicht verwendet werden.

Wenn diese Funktionsaufrufe erfolgreich sind, muss der Puffer schließlich durch Aufruf von `releaselockedbufferproc` freigegeben werden, wobei das ursprüngliche Objekt als Argument übergeben wird. Die Funktion `releaselockedbufferproc` kann nicht fehlschlagen. Für Objekte, die tatsächlich eine interne Sperrzahl führen, wäre es ein fataler Fehler, wenn die Funktion `releaselockedbufferproc` zu oft aufgerufen würde, was zu einer negativen Sperrzahl führen würde.

Ähnlich wie bei der „alten“ Puffer-Schnittstelle können alle diese Funktionen auf NULL gesetzt werden, aber es wird dringend empfohlen, die Funktion `releaselockedbufferproc` zu implementieren (auch wenn sie nichts tut), wenn eine der Funktionen `acquireread`/`writelockedbufferproc` implementiert ist, um zu verhindern, dass Erweiterungsschreiber auf NULL prüfen und sie nicht aufrufen.

Diese Funktionen sollen nicht direkt aufgerufen werden, sondern über Hilfsfunktionen aufgerufen werden, die in `Include/abstract.h` deklariert sind.

int PyObject_AcquireLockedReadBuffer(PyObject *obj,
                                    const void **buffer,
                                    size_t *buffer_len);

int PyObject_AcquireLockedWriteBuffer(PyObject *obj,
                                      void **buffer,
                                      size_t *buffer_len);

void PyObject_ReleaseLockedBuffer(PyObject *obj);

Die beiden erstgenannten Funktionen geben 0 im Erfolgsfall zurück, setzen `buffer` auf den Speicherort und `buffer_len` auf die Länge des Speicherblocks in Bytes. Im Fehlerfall oder wenn die gesperrte Puffer-Schnittstelle nicht von `obj` implementiert wird, geben sie -1 zurück und setzen eine Ausnahme.

Die letztere Funktion gibt nichts zurück und kann nicht fehlschlagen.

Abwärtskompatibilität

Die Größe der Struktur `PyBufferProcs` ändert sich, wenn dieser Vorschlag implementiert wird, aber der `tp_flags`-Slot des Typs kann verwendet werden, um festzustellen, ob die zusätzlichen Felder vorhanden sind.

Referenzimplementierung

Eine Implementierung wurde auf dem SourceForge Patch Manager unter https://bugs.python.org/issue652857 hochgeladen.

Zusätzliche Hinweise/Kommentare

Python-Strings, Unicode-Strings, mmap-Objekte und Array-Objekte würden die gesperrte Puffer-Schnittstelle exponieren.

mmap- und Array-Objekte würden während der aktiven Pufferstellung in einen gesperrten Zustand übergehen; dies ist für Strings und Unicode-Objekte nicht erforderlich. Das Ändern der Größe gesperrter Array-Objekte ist nicht gestattet und löst eine Ausnahme aus. Ob das Schließen eines gesperrten mmap-Objekts ein Fehler ist oder erst verzögert wird, bis die Sperrzahl Null erreicht, ist ein Implementierungsdetail.

Guido empfiehlt:

Aber ich mache mir immer noch große Sorgen, dass es zu einfach ist, dass eine Erweiterung scheinbar funktioniert, während sie vergisst, den Puffer freizugeben, wenn die meisten integrierten Typen (z. B. Strings, Bytes) die Freigabefunktionalität nicht implementieren.

Ich empfehle, dass zumindest einige integrierte Typen die Erfassungs-/Freigabefunktionalität mit einem Zähler implementieren und beim Löschen des Objekts ASSERTieren, dass der Zähler Null ist – wenn der ASSERT fehlschlägt, hat jemand seine Referenz auf das Objekt DECREF’t, ohne es freizugeben. (Die Regel sollte lauten, dass man eine Referenz auf das Objekt besitzen muss, solange man das Objekt erfasst hat.)

Für Strings könnte dies unpraktikabel sein, da das String-Objekt 4 Bytes wachsen müsste, um den Zähler zu speichern; aber das neue Bytes-Objekt (PEP 296) könnte den Zähler problemlos implementieren, und das Array-Objekt auch – so gibt es reichlich Gelegenheit, die korrekte Verwendung des Protokolls zu testen.

Feedback der Community

Greg Ewing bezweifelt, dass die gesperrte Puffer-Schnittstelle überhaupt benötigt wird; er meint, die normale Puffer-Schnittstelle könnte verwendet werden, wenn der Zeiger jedes Mal (neu) abgerufen wird, wenn er verwendet wird. Dies scheint gefährlich zu sein, da selbst harmlos aussehende Aufrufe der Python-API wie `Py_DECREF()` die Ausführung von beliebigem Python-Code auslösen können.

Die erste Version dieses Vorschlags enthielt keine Freigabefunktion, aber es stellte sich heraus, dass dies zu restriktiv gewesen wäre: mmap- und Array-Objekte hätten sie nicht implementieren können, da mmap-Objekte jederzeit geschlossen werden können, wenn sie nicht gesperrt sind, und Array-Objekte den Puffer verkleinern oder neu zuweisen könnten.

Diese PEP wird wahrscheinlich abgelehnt, da sie niemand außer dem Autor benötigt.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0298.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT