Hinweis: Diese Frage wurde ursprünglich hier gestellt, aber die Kopfgeldzeit ist abgelaufen, obwohl keine akzeptable Antwort gefunden wurde. Ich stelle diese Frage erneut, einschließlich aller Details in der ursprünglichen Frage.
Ein Python-Skript führt alle 60 Sekunden eine Reihe von Klassenfunktionen mit dem Schedul- Modul aus:
# sc is a sched.scheduler instance
sc.enter(60, 1, self.doChecks, (sc, False))
Das Skript wird als daemonisierter Prozess ausgeführt, wobei der Code hier verwendet wird .
Eine Reihe von Klassenmethoden , die aufgerufen werden als Teil doChecks verwenden , um die subprocess Modul Systemfunktionen aufrufen , um System - Statistiken zu erhalten:
ps = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE).communicate()[0]
Dies läuft einige Zeit einwandfrei, bevor das gesamte Skript mit dem folgenden Fehler abstürzt:
File "/home/admin/sd-agent/checks.py", line 436, in getProcesses
File "/usr/lib/python2.4/subprocess.py", line 533, in __init__
File "/usr/lib/python2.4/subprocess.py", line 835, in _get_handles
OSError: [Errno 12] Cannot allocate memory
Die Ausgabe von free -m auf dem Server nach dem Absturz des Skripts lautet:
$ free -m
total used free shared buffers cached
Mem: 894 345 549 0 0 0
-/+ buffers/cache: 345 549
Swap: 0 0 0
Auf dem Server wird CentOS 5.3 ausgeführt. Ich kann weder auf meinen eigenen CentOS-Boxen noch mit anderen Benutzern reproduzieren, die das gleiche Problem melden.
Ich habe eine Reihe von Dingen versucht, um dies zu debuggen, wie in der ursprünglichen Frage vorgeschlagen:
Protokollierung der Ausgabe von free -m vor und nach dem Popen-Aufruf. Die Speichernutzung ändert sich nicht wesentlich, dh der Speicher wird während der Ausführung des Skripts nicht allmählich aufgebraucht.
Ich habe dem Popen-Aufruf close_fds = True hinzugefügt, aber das machte keinen Unterschied - das Skript stürzte immer noch mit demselben Fehler ab. Empfohlene hier und hier .
Ich habe die Grenzwerte überprüft, die (-1, -1) sowohl bei RLIMIT_DATA als auch bei RLIMIT_AS zeigten, wie hier vorgeschlagen .
Ein Artikel schlug der keinen Auslagerungsspeicher mit der Ursache sein , aber Swap ist auf Nachfrage tatsächlich zur Verfügung (nach der Web - Host) , und dies wurde auch vorgeschlagen , als ein falsche verursacht hier .
Die Prozesse werden geschlossen, da dies das Verhalten der Verwendung von .communicate () ist, das durch den Python-Quellcode und die Kommentare hier gesichert wird .
Die gesamten Prüfungen finden Sie hier auf GitHub mit der in Zeile 442 definierten Funktion getProcesses. Dies wird von doChecks () ab Zeile 520 aufgerufen.
Das Skript wurde vor dem Absturz mit der folgenden Ausgabe ausgeführt:
recv(4, "Total Accesses: 516662\nTotal kBy"..., 234, 0) = 234
gettimeofday({1250893252, 887805}, NULL) = 0
write(3, "2009-08-21 17:20:52,887 - checks"..., 91) = 91
gettimeofday({1250893252, 888362}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 74) = 74
gettimeofday({1250893252, 888897}, NULL) = 0
write(3, "2009-08-21 17:20:52,888 - checks"..., 67) = 67
gettimeofday({1250893252, 889184}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 81) = 81
close(4) = 0
gettimeofday({1250893252, 889591}, NULL) = 0
write(3, "2009-08-21 17:20:52,889 - checks"..., 63) = 63
pipe([4, 5]) = 0
pipe([6, 7]) = 0
fcntl64(7, F_GETFD) = 0
fcntl64(7, F_SETFD, FD_CLOEXEC) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7f12708) = -1 ENOMEM (Cannot allocate memory)
write(2, "Traceback (most recent call last"..., 35) = 35
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 52) = 52
open("/home/admin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/daemon.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/home/admin/sd-agent/dae"..., 60) = 60
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/agent.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/agent."..., 54) = 54
open("/usr/lib/python2.4/sched.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/sched"..., 55) = 55
fstat64(8, {st_mode=S_IFREG|0644, st_size=4054, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "\"\"\"A generally useful event sche"..., 4096) = 4054
write(2, " ", 4) = 4
write(2, "void = action(*argument)\n", 25) = 25
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 60) = 60
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/bin/sd-agent/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python24.zip/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/plat-linux2/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOMEM (Cannot allocate memory)
open("/usr/lib/python2.4/lib-tk/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/lib-dynload/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
open("/usr/lib/python2.4/site-packages/checks.py", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
write(2, " File \"/usr/bin/sd-agent/checks"..., 64) = 64
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 65) = 65
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "errread, errwrite)\n", 19) = 19
close(8) = 0
munmap(0xb7d28000, 4096) = 0
open("/usr/lib/python2.4/subprocess.py", O_RDONLY|O_LARGEFILE) = 8
write(2, " File \"/usr/lib/python2.4/subpr"..., 71) = 71
fstat64(8, {st_mode=S_IFREG|0644, st_size=39931, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7d28000
read(8, "# subprocess - Subprocesses with"..., 4096) = 4096
read(8, "lso, the newlines attribute of t"..., 4096) = 4096
read(8, "code < 0:\n print >>sys.st"..., 4096) = 4096
read(8, "alse does not exist on 2.2.0\ntry"..., 4096) = 4096
read(8, " p2cread\n # c2pread <-"..., 4096) = 4096
read(8, "table(self, handle):\n "..., 4096) = 4096
read(8, "rrno using _sys_errlist (or siml"..., 4096) = 4096
read(8, " p2cwrite = None, None\n "..., 4096) = 4096
write(2, " ", 4) = 4
write(2, "self.pid = os.fork()\n", 21) = 21
close(8) = 0
munmap(0xb7d28000, 4096) = 0
write(2, "OSError", 7) = 7
write(2, ": ", 2) = 2
write(2, "[Errno 12] Cannot allocate memor"..., 33) = 33
write(2, "\n", 1) = 1
unlink("/var/run/sd-agent.pid") = 0
close(3) = 0
munmap(0xb7e0d000, 4096) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x589978}, {0xb89a60, [], SA_RESTORER, 0x589978}, 8) = 0
brk(0xa022000) = 0xa022000
exit_group(1) = ?
/var/log/messages
oderdmesg
Befehl.Antworten:
Als allgemeine Regel gilt (dh in Vanille-Kernel),
fork
/clone
Ausfälle mitENOMEM
speziell auftreten , weil entweder eine ehrliche Gott out-of-memory Bedingung (dup_mm
,dup_task_struct
,alloc_pid
,mpol_dup
,mm_init
usw. quaken), oder weilsecurity_vm_enough_memory_mm
Sie es versäumt , während der Durchsetzung der overcommit Politik .Überprüfen Sie zunächst die vmsize des Prozesses, bei dem der Verzweigungsversuch zum Zeitpunkt des Verzweigungsversuchs fehlgeschlagen ist, und vergleichen Sie dann die Menge an freiem Speicher (physisch und Swap) in Bezug auf die Überbindungsrichtlinie (geben Sie die Zahlen ein).
Beachten Sie in Ihrem speziellen Fall, dass Virtuozzo zusätzliche Kontrollen bei der Durchsetzung von Überbindungen durchführt . Darüber hinaus bin ich mir nicht sicher, wie viel Kontrolle Sie wirklich innerhalb Ihres Containers über die Swap- und Overcommit-Konfiguration haben (um das Ergebnis der Durchsetzung zu beeinflussen).
Um tatsächlich vorwärts zu kommen, würde ich sagen, dass Sie zwei Möglichkeiten haben :
HINWEIS : Der Codierungsaufwand ist möglicherweise umsonst, wenn sich herausstellt, dass Sie es nicht sind, sondern ein anderer Typ, der in einer anderen Instanz auf demselben Server wie amock ausgeführt wurde.
Speicher-weise, wir wissen bereits , dass
subprocess.Popen
Verwendungenfork
/clone
unter der Haube was bedeutet, dass jedes Mal , wenn Sie es nennen Sie anfordernden einmal mehr so viel Speicher wie Python bereits frisst , das heißt in den Hunderten von zusätzlichen MB, alle um dannexec
eine mickrige 10kB ausführbare Datei wiefree
oderps
. Im Falle einer ungünstigen Überbindungsrichtlinie werden Sie bald sehenENOMEM
.Alternativen dazu
fork
haben nicht diese übergeordneten Seitentabellen etc. Kopierprobleme sindvfork
undposix_spawn
. Aber wenn Sie nicht das Gefühl , wie Stücke von Umschreibensubprocess.Popen
in Bezug aufvfork
/posix_spawn
, prüft , mitsuprocess.Popen
nur einmal, am Anfang des Skripts (wenn Pythons Speicherbedarf ist minimal), um einen Shell - Skript zu erzeugen , dass dann läuftfree
/ps
/sleep
und was sonst noch in einem Schleife parallel zu Ihrem Skript; Fragen Sie die Ausgabe des Skripts ab oder lesen Sie sie synchron, möglicherweise aus einem separaten Thread, wenn Sie andere Dinge asynchron erledigen müssen. Führen Sie die Datenverarbeitung in Python durch, überlassen Sie das Forking jedoch dem untergeordneten Prozess.JEDOCH , in Ihrem speziellen Fall können Sie überspringen aufrufen
ps
undfree
zusammen; Diese Informationen stehen Ihnen in Python direkt zur Verfügungprocfs
, unabhängig davon , ob Sie selbst oder über vorhandene Bibliotheken und / oder Pakete darauf zugreifen . Wennps
undfree
waren die einzigen Dienstprogramme, die Sie ausgeführt haben, können Sie diesesubprocess.Popen
vollständig abschaffen .Was auch immer Sie tun
subprocess.Popen
, wenn Ihr Skript Speicher verliert, werden Sie schließlich immer noch an die Wand stoßen. Behalten Sie es im Auge und überprüfen Sie es auf Speicherlecks .quelle
gc.collect()
kurz zuvorsubprocess.Popen
in den Fällen hilft, in denen der Garbage Collector eine Weile nicht ausgeführt wurde./proc/fd/maps
Wenn
free -m
ich mir die Ausgabe anschaue, scheint mir, dass Sie tatsächlich keinen Swap-Speicher zur Verfügung haben. Ich bin nicht sicher, ob unter Linux der Swap bei Bedarf immer automatisch verfügbar ist, aber ich hatte das gleiche Problem und keine der Antworten hier hat mir wirklich geholfen. Das Hinzufügen von Swap-Speicher hat das Problem in meinem Fall jedoch behoben. Da dies anderen Personen helfen könnte, die mit demselben Problem konfrontiert sind, poste ich meine Antwort zum Hinzufügen eines 1-GB-Swaps (unter Ubuntu 12.04, sollte dies jedoch für andere Distributionen ähnlich funktionieren).Sie können zunächst überprüfen, ob ein Swap-Speicher aktiviert ist.
Wenn es leer ist, bedeutet dies, dass kein Swap aktiviert ist. So fügen Sie einen 1-GB-Swap hinzu:
Fügen Sie die folgende Zeile hinzu
fstab
, um den Swap dauerhaft zu machen.Quelle und weitere Informationen finden Sie hier .
quelle
Swap ist möglicherweise nicht der zuvor vorgeschlagene rote Hering. Wie groß ist der fragliche Python-Prozess kurz vor dem
ENOMEM
?Steuert unter Kernel 2.6,
/proc/sys/vm/swappiness
wie aggressiv sich der Kernel zum Tauschen dreht, undovercommit*
legt fest, wie viel und wie genau der Kernel den Speicher mit einem Augenzwinkern und einem Nicken aufteilen kann. Wie Ihr Facebook-Beziehungsstatus ist es kompliziert .Dies entspricht jedoch nicht der Ausgabe Ihres
free(1)
Befehls, der keinen von Ihrer Serverinstanz erkannten Swap Space anzeigt. Nun, Ihr Webhost weiß sicherlich viel mehr als ich über dieses Thema, aber virtuelle RHEL / CentOS-Systeme, die ich verwendet habe, haben gemeldet, dass der Austausch für das Gastbetriebssystem verfügbar ist.Anpassen von Red Hat KB Artikel 15252 :
Vergleichen Sie Ihre
/proc/sys/vm
Einstellungen mit einer einfachen CentOS 5.3-Installation. Fügen Sie eine Auslagerungsdatei hinzu. Ratsche runterswappiness
und schau, ob du noch länger lebst.quelle
ps -o user,pid,vsz="Mem(Kb)" -o cmd $PYTHON_PID
oder top (1) sollte es tun.Für eine einfache Lösung könnten Sie
Wenn Sie sicher sind, dass Ihr System über genügend Speicher verfügt. Siehe Linux über Commit-Heuristik .
quelle
Ich vermute weiterhin, dass Ihr Kunde / Benutzer ein Kernelmodul oder einen Treiber geladen hat, der den
clone()
Systemaufruf stört (möglicherweise eine obskure Sicherheitsverbesserung, etwas wie LIDS, aber dunkler?) Oder einige der Kerneldatenstrukturen auffüllt, die dies tun ist notwendig für diefork()
/clone()
den Betrieb (Prozesstabelle, Seite Tabellen, Dateideskriptors Tabellen usw.).Hier ist der relevante Teil der
fork(2)
Manpage:Ich schlage vor, dass der Benutzer dies nach dem Booten in einen allgemeinen Kernel und mit nur einem minimalen Satz geladener Module und Treiber versucht (mindestens erforderlich, um Ihre Anwendung / Ihr Skript auszuführen). Von dort aus können sie unter der Annahme, dass es in dieser Konfiguration funktioniert, eine binäre Suche zwischen dieser und der Konfiguration durchführen, bei der das Problem auftritt. Dies ist die Standard-Fehlerbehebung für Systemadministratoren 101.
Die relevante Zeile in Ihrem
strace
ist:... Ich weiß, dass andere über Swap und Speicherverfügbarkeit gesprochen haben (und ich würde empfehlen, dass Sie mindestens eine kleine Swap-Partition einrichten, ironischerweise auch, wenn sie sich auf einer RAM-Disk befindet ... die Codepfade durch den Linux-Kernel, wenn dies der Fall ist Sogar ein winziger Teil des verfügbaren Swaps wurde weitaus umfangreicher ausgeübt als diejenigen (Ausnahmebehandlungspfade), in denen kein Swap verfügbar ist.
Ich vermute jedoch, dass dies immer noch ein roter Hering ist.
Die Tatsache, dass
free
0 (ZERO) Speicher gemeldet wird, der vom Cache und den Puffern verwendet wird, ist sehr störend. Ich vermute, dass diefree
Ausgabe ... und möglicherweise Ihr Anwendungsproblem hier durch ein proprietäres Kernelmodul verursacht werden, das die Speicherzuordnung in irgendeiner Weise stört.Laut den Manpages für fork () / clone () sollte der Systemaufruf fork () EAGAIN zurückgeben, wenn Ihr Aufruf eine Verletzung des Ressourcenlimits verursachen würde (RLIMIT_NPROC) ... es heißt jedoch nicht, ob EAGAIN zurückgegeben werden soll durch andere Verstöße gegen RLIMIT *. In jedem Fall kann dies zu einem -ENOMEM-Fehler führen, wenn Ihr Ziel / Host über seltsame Vormetric- oder andere Sicherheitseinstellungen verfügt (oder wenn Ihr Prozess unter einer seltsamen SELinux-Richtlinie ausgeführt wird).
Es ist ziemlich unwahrscheinlich, dass es sich um ein normales Linux / UNIX-Problem handelt. Dort ist etwas Ungewöhnliches los.
quelle
Haben Sie versucht, Folgendes zu verwenden:
Ich dachte, das hätte genau das gleiche Problem für mich behoben. Aber dann wurde mein Prozess getötet, anstatt nicht zu spawnen, was noch schlimmer ist.
Nach einigen Tests stellte ich fest, dass dies nur bei älteren Python-Versionen auftrat: Es passiert mit 2.6.5, aber nicht mit 2.7.2
Meine Suche hatte mich hierher geführt python-close_fds-problem , aber das Deaktivieren von closed_fds hatte das Problem nicht gelöst. Es ist immer noch eine Lektüre wert.
Ich fand heraus, dass Python Dateideskriptoren leckte, indem ich nur ein Auge darauf hatte:
Wie Sie möchte ich die Ausgabe des Befehls erfassen und OOM-Fehler vermeiden ... aber es sieht so aus, als ob die einzige Möglichkeit für Benutzer darin besteht, eine weniger fehlerhafte Version von Python zu verwenden. Nicht ideal...
quelle
Ich habe schlampigen Code gesehen, der so aussieht:
Sie sollten überprüfen, ob dies im Python-Code geschieht. Errno ist nur gültig, wenn der fortlaufende Systemaufruf fehlgeschlagen ist.
Bearbeitet, um hinzuzufügen:
Sie sagen nicht, wie lange dieser Prozess dauert. Mögliche Speicherkonsumenten
quelle
errno
auf dem Weg viele Male zurückgesetzt wird.Vielleicht kannst du es einfach
Es funktioniert für meinen Fall.
Referenz: https://github.com/openai/gym/issues/110#issuecomment-220672405
quelle