Werkzeug: penggunaan memori pelarian pada 0.15.x

Dibuat pada 23 Apr 2019  ·  11Komentar  ·  Sumber: pallets/werkzeug

Saya telah mempersempitnya menjadi skrip kecil ini, belum menggalinya tetapi pistol merokok adalah hal kompilasi fungsi baru

from werkzeug.routing import Map, Rule


def main():
    while True:
        Map([Rule('/a/<string:b>')])


if __name__ == '__main__':
    exit(main())
bug routing

Komentar yang paling membantu

Ini cukup untuk memutus siklus dan menghilangkan kebocoran memori:

diff --git a/src/werkzeug/routing.py b/src/werkzeug/routing.py
index c7cff94d..8176ddfe 100644
--- a/src/werkzeug/routing.py
+++ b/src/werkzeug/routing.py
@@ -1254,13 +1254,13 @@ class BaseConverter(object):
     weight = 100

     def __init__(self, map):
-        self.map = map
+        self.charset = map.charset

     def to_python(self, value):
         return value

     def to_url(self, value):
-        return _fast_url_quote(text_type(value).encode(self.map.charset))
+        return _fast_url_quote(text_type(value).encode(self.charset))


 class UnicodeConverter(BaseConverter):

meskipun saya percaya penyebab sebenarnya dari siklus adalah bahwa LOAD_CONST digunakan dalam objek kode terhadap objek yang bukan konstanta itu sendiri, saya percaya ini membingungkan gc (dengan asumsi bahwa objek kode yang dirujuk hidup, ketika mereka sebenarnya tidak)

Semua 11 komentar

@edk0

Sepertinya Anda mengatakan bahwa aturan tidak di-GC dengan benar?

Saya tidak jelas dalam keadaan nyata apa ini akan terjadi. Biasanya Anda menentukan seperangkat aturan dan kemudian menggunakannya, aturan tersebut tidak dibuat dan dihapus secara sewenang-wenang.

penggunaan kami di sini melibatkan satu set pengalihan yang ditentukan dalam file konfigurasi, file konfigurasi ini berubah secara berkala saat kami menambah atau menghapus rute rias

alih-alih menerapkan aplikasi setiap kali kami ingin menambahkan rute rias, kami cukup memperbarui file konfigurasi dan aplikasi membangun kembali pemetaan rute (yang digunakan oleh aplikasi flask untuk melayani pengalihan)

Hmm, itu biasanya tidak disarankan karena perubahan pada peta tidak disinkronkan di pekerja multiproses. Saya mungkin akan menerapkannya sebagai penangan kesalahan untuk 404, untuk memeriksa apakah pengalihan harus dikembalikan sebagai gantinya. Tidak mengatakan itu tidak boleh diperbaiki, hanya saja itu bukan kasus penggunaan yang pernah saya dengar.

setiap pekerja individu memeriksa file konfigurasi secara berkala dan memuatnya kembali - itu tidak disuntikkan langsung ke aplikasi labu sejauh yang saya tahu

bagaimanapun juga, objek-objek ini mungkin tidak boleh bocor :laughing: -- Saya sedang mencari tahu apa penyebabnya -- Saya menduga fungsi yang dikompilasi memiliki masalah atau hashing (karena Map tampaknya untuk juga terlibat dalam beberapa cara)

Sebagai catatan, kebocoran ini tidak terjadi tanpa bagian <string:b>

inilah pembongkaran objek fungsi yang dibuatnya:

>>> x = Rule('/a/<string:b>')
>>> from werkzeug.routing import Map
>>> y = Map([x])
>>> x._build
<function <builder:'/a/<string:b>'> at 0x7f16d62d1730>
>>> import dis
>>> dis.dis(x._build)
  1           0 LOAD_CONST               0 ('')
              2 LOAD_CONST               1 ('/a/')
              4 LOAD_CONST               2 (<bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f16d2c9a0f0>>)
              6 LOAD_FAST                0 (b)
              8 CALL_FUNCTION            1
             10 BUILD_STRING             2
             12 BUILD_TUPLE              2
             14 RETURN_VALUE
>>> dis.dis(x._build_unknown)
  1           0 LOAD_CONST               0 ('')
              2 LOAD_CONST               1 ('/a/')
              4 LOAD_CONST               2 (<bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f16d2c9a0f0>>)
              6 LOAD_FAST                0 (b)
              8 CALL_FUNCTION            1
             10 LOAD_FAST                1 (.keyword_arguments)
             12 JUMP_IF_TRUE_OR_POP     20
             14 LOAD_CONST               0 ('')
             16 DUP_TOP
             18 JUMP_FORWARD            10 (to 30)
        >>   20 LOAD_CONST               3 (functools.partial(<function url_encode at 0x7f16d2d5d510>, charset='utf-8', sort=False, key=None))
             22 ROT_TWO
             24 CALL_FUNCTION            1
             26 LOAD_CONST               4 ('?')
             28 ROT_TWO
        >>   30 BUILD_STRING             4
             32 BUILD_TUPLE              2
             34 RETURN_VALUE

Menyesuaikan skrip sedikit:

import collections
import gc
import pprint
from werkzeug.routing import Map, Rule


def main():
    for _ in range(10000):
        Map([Rule('/a/<string:b>')])
    for _ in range(5):
        gc.collect()
    counts = collections.Counter(type(o) for o in gc.get_objects())
    pprint.pprint(counts.most_common(15))


if __name__ == '__main__':
    exit(main())

sepertinya bocor (setidaknya, mungkin lebih banyak pada tipe umum lainnya di atasnya juga) Map , Rule , serta functools.partial dan UnicodeConverter per panggilan:

$ ./venv/bin/python t.py
[(<class 'dict'>, 62085),
 (<class 'list'>, 50514),
 (<class 'function'>, 24085),
 (<class 'tuple'>, 21811),
 (<class 'method'>, 20032),
 (<class 'set'>, 10518),
 (<class 'functools.partial'>, 10002),
 (<class 'werkzeug.routing.UnicodeConverter'>, 10000),
 (<class 'werkzeug.routing.Map'>, 10000),
 (<class 'werkzeug.routing.Rule'>, 10000),
 (<class 'weakref'>, 1306),
 (<class 'wrapper_descriptor'>, 1131),
 (<class 'method_descriptor'>, 879),
 (<class 'builtin_function_or_method'>, 839),
 (<class 'getset_descriptor'>, 740)]

inilah beberapa grafik hal-hal yang membuat ini tetap hidup di gc:

def graph(obj, ids, *, seen=None, indent='', limit=10):
    if seen is None:
        seen = set()

    for referrer in gc.get_referrers(obj):
        # the main frame which has a hard reference to ths object
        if (
                type(referrer).__name__ == 'frame' and
                referrer.f_globals['__name__'] == '__main__'
        ):
            continue
        # objects only present due to traversal of gc referrers
        elif id(referrer) not in ids:
            continue
        elif id(referrer) in seen:
            print(f'{indent}(already seen) {id(referrer)}')
            continue

        seen.add(id(referrer))

        if indent == '':
            print('=' * 79)
        print(f'{indent}type: {type(referrer).__name__} ({id(referrer)})')
        fmted = repr(referrer)  #pprint.pformat(referrer)
        print(indent + fmted.replace('\n', f'\n{indent}'))

        if limit:
            graph(
                referrer, ids,
                seen=seen, indent='==' + indent, limit=limit - 1,
            )

...

    ids = {id(o) for o in gc.get_objects()}
    obj = next(iter(o for o in gc.get_objects() if isinstance(o, Map)))
    graph(obj, ids)
===============================================================================
type: dict (140272699432680)
{'map': Map([<Rule '/a/<b>' -> None>]), 'regex': '[^/]{1,}'}
==type: UnicodeConverter (140272699175656)
==<werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>
====type: method (140272750734216)
====<bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>>
======type: tuple (140272726348928)
======('', '/a/', <bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>>)
====type: method (140272749846664)
====<bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>>
======type: tuple (140272726372424)
======('', '/a/', <bound method BaseConverter.to_url of <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>>, functools.partial(<function url_encode at 0x7f93c877eae8>, charset='utf-8', sort=False, key=None), '?')
====type: dict (140272723298056)
===={'b': <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>}
======type: dict (140272749460432)
======{'rule': '/a/<string:b>', 'is_leaf': True, 'map': Map([<Rule '/a/<b>' -> None>]), 'strict_slashes': True, 'subdomain': '', 'host': None, 'defaults': None, 'build_only': False, 'alias': False, 'methods': None, 'endpoint': None, 'redirect_to': None, 'arguments': {'b'}, '_trace': [(False, '|'), (False, '/a/'), (True, 'b')], '_converters': {'b': <werkzeug.routing.UnicodeConverter object at 0x7f93c867f2e8>}, '_regex': re.compile('^\\|\\/a\\/(?P<b>[^/]{1,})$'), '_argument_weights': [100], '_static_weights': [(0, -1)], '_build': <function <builder:'/a/<string:b>'> at 0x7f93c9d880d0>, '_build_unknown': <function <builder:'/a/<string:b>'> at 0x7f93c9d88158>}
========type: Rule (140272749480984)
========<Rule '/a/<b>' -> None>
==========type: list (140272699123848)
==========[<Rule '/a/<b>' -> None>]
============type: dict (140272726280736)
============{'_rules': [<Rule '/a/<b>' -> None>], '_rules_by_endpoint': {None: [<Rule '/a/<b>' -> None>]}, '_remap': False, '_remap_lock': <unlocked _thread.lock object at 0x7f93cb6d9da0>, 'default_subdomain': '', 'charset': 'utf-8', 'encoding_errors': 'replace', 'strict_slashes': True, 'redirect_defaults': True, 'host_matching': False, 'converters': {'default': <class 'werkzeug.routing.UnicodeConverter'>, 'string': <class 'werkzeug.routing.UnicodeConverter'>, 'any': <class 'werkzeug.routing.AnyConverter'>, 'path': <class 'werkzeug.routing.PathConverter'>, 'int': <class 'werkzeug.routing.IntegerConverter'>, 'float': <class 'werkzeug.routing.FloatConverter'>, 'uuid': <class 'werkzeug.routing.UUIDConverter'>}, 'sort_parameters': False, 'sort_key': None}
==============type: Map (140272749480872)
==============Map([<Rule '/a/<b>' -> None>])
================(already seen) 140272699432680
================(already seen) 140272749460432
==========type: list (140272700110792)
==========[<Rule '/a/<b>' -> None>]
============type: dict (140272726280808)
============{None: [<Rule '/a/<b>' -> None>]}
==============(already seen) 140272726280736
(already seen) 140272749460432

Ini cukup untuk memutus siklus dan menghilangkan kebocoran memori:

diff --git a/src/werkzeug/routing.py b/src/werkzeug/routing.py
index c7cff94d..8176ddfe 100644
--- a/src/werkzeug/routing.py
+++ b/src/werkzeug/routing.py
@@ -1254,13 +1254,13 @@ class BaseConverter(object):
     weight = 100

     def __init__(self, map):
-        self.map = map
+        self.charset = map.charset

     def to_python(self, value):
         return value

     def to_url(self, value):
-        return _fast_url_quote(text_type(value).encode(self.map.charset))
+        return _fast_url_quote(text_type(value).encode(self.charset))


 class UnicodeConverter(BaseConverter):

meskipun saya percaya penyebab sebenarnya dari siklus adalah bahwa LOAD_CONST digunakan dalam objek kode terhadap objek yang bukan konstanta itu sendiri, saya percaya ini membingungkan gc (dengan asumsi bahwa objek kode yang dirujuk hidup, ketika mereka sebenarnya tidak)

melalui #1524

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

golf-player picture golf-player  ·  10Komentar

abathur picture abathur  ·  13Komentar

lepture picture lepture  ·  6Komentar

sorenh picture sorenh  ·  4Komentar

davidism picture davidism  ·  9Komentar