Werkzeug: 0.15.x의 λŸ°μ–΄μ›¨μ΄ λ©”λͺ¨λ¦¬ μ‚¬μš©λŸ‰

에 λ§Œλ“  2019λ…„ 04μ›” 23일  Β·  11μ½”λ©˜νŠΈ  Β·  좜처: pallets/werkzeug

이 μž‘μ€ 슀크립트둜 λ²”μœ„λ₯Ό μ’ν˜”κ³  아직 파고 λ“€μ§€λŠ” μ•Šμ•˜μ§€λ§Œ 흑연 총은 μƒˆλ‘œμš΄ κΈ°λŠ₯ 컴파일 ν•­λͺ©μž…λ‹ˆλ‹€.

from werkzeug.routing import Map, Rule


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


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

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

이것은 μ£ΌκΈ°λ₯Ό κΉ¨κ³  λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μ œκ±°ν•˜κΈ°μ— μΆ©λΆ„ν•©λ‹ˆλ‹€.

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):

μ‚¬μ΄ν΄μ˜ μ‹€μ œ 원인은 LOAD_CONST μžμ²΄κ°€ μƒμˆ˜κ°€ μ•„λ‹Œ 객체에 λŒ€ν•΄ μ½”λ“œ κ°μ²΄μ—μ„œ μ‚¬μš©λ˜κ³  μžˆλ‹€λŠ” 점이라고 μƒκ°ν•˜μ§€λ§Œ, 이것이 gcλ₯Ό ν˜Όλž€μŠ€λŸ½κ²Œ ν•œλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€(참쑰된 μ½”λ“œ 객체가 라이브라고 κ°€μ •ν•  λ•Œ 그듀은 μ‹€μ œλ‘œ 그렇지 μ•ŠμŠ΅λ‹ˆλ‹€)

λͺ¨λ“  11 λŒ“κΈ€

@ edk0

κ·œμΉ™μ΄ μ˜¬λ°”λ₯΄κ²Œ GCλ˜μ§€ μ•ŠλŠ”λ‹€κ³  λ§ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆκΉŒ?

μ–΄λ–€ μ‹€μ œ μƒν™©μ—μ„œ 이런 일이 λ°œμƒν•˜λŠ”μ§€ ν™•μ‹€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 일반적으둜 κ·œμΉ™ 집합을 μ •μ˜ν•œ λ‹€μŒ μ‚¬μš©ν•˜λ©° μž„μ˜λ‘œ 생성 및 μ‚­μ œλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ 우리의 μ‚¬μš©λ²•μ€ ꡬ성 νŒŒμΌμ— μ •μ˜λœ 일련의 λ¦¬λ””λ ‰μ…˜μ„ ν¬ν•¨ν•©λ‹ˆλ‹€. 이 ꡬ성 νŒŒμΌμ€ 가상 경둜λ₯Ό μΆ”κ°€ν•˜κ±°λ‚˜ μ œκ±°ν•  λ•Œ 주기적으둜 λ³€κ²½λ©λ‹ˆλ‹€.

λ² λ‹ˆν‹° 라우트λ₯Ό μΆ”κ°€ν•  λ•Œλ§ˆλ‹€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ°°ν¬ν•˜λŠ” λŒ€μ‹  ꡬ성 νŒŒμΌμ„ μ—…λ°μ΄νŠΈν•˜κΈ°λ§Œ ν•˜λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ 라우트 맀핑을 λ‹€μ‹œ λΉŒλ“œν•©λ‹ˆλ‹€(μ΄λŠ” flask μ•±μ—μ„œ λ¦¬λ””λ ‰μ…˜μ„ μ œκ³΅ν•˜λŠ” 데 μ‚¬μš©λ¨).

흠, 맡에 λŒ€ν•œ λ³€κ²½ 사항이 닀쀑 ν”„λ‘œμ„ΈμŠ€ μž‘μ—…μžμ—μ„œ λ™κΈ°ν™”λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— 일반적으둜 ꢌμž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ¦¬λ””λ ‰μ…˜μ΄ λŒ€μ‹  λ°˜ν™˜λ˜μ–΄μ•Ό ν•˜λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ 404에 λŒ€ν•œ 였λ₯˜ 처리기둜 λŒ€μ‹  κ΅¬ν˜„ν•  κ²ƒμž…λ‹ˆλ‹€. μˆ˜μ •λ˜μ–΄μ„œλŠ” μ•ˆλœλ‹€λŠ” 것이 μ•„λ‹ˆλΌ μ œκ°€ λ“€μ–΄λ³Έ μ‚¬μš© 사둀가 μ•„λ‹ˆλΌλŠ” κ²ƒμž…λ‹ˆλ‹€.

각 κ°œλ³„ μž‘μ—…μžλŠ” ꡬ성 νŒŒμΌμ„ 주기적으둜 ν™•μΈν•˜κ³  λ‹€μ‹œ λ‘œλ“œν•©λ‹ˆλ‹€. λ‚΄κ°€ 말할 수 μžˆλŠ” ν•œ ν”ŒλΌμŠ€ν¬ 앱에 직접 μ£Όμž…λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μ–΄λŠ μͺ½μ΄λ“ , μ΄λŸ¬ν•œ κ°œμ²΄λŠ” λˆ„μΆœλ˜μ–΄μ„œλŠ” μ•ˆ λ©λ‹ˆλ‹€. μ›ƒμŒ: -- 원인을 μ°Ύκ³  μžˆμŠ΅λ‹ˆλ‹€. -- 컴파일 쀑인 ν•¨μˆ˜μ— λ¬Έμ œκ°€ μžˆκ±°λ‚˜ ν•΄λ‹Ή ν•¨μˆ˜μ˜ 해싱이 μžˆλŠ” κ²ƒμœΌλ‘œ μ˜μ‹¬λ©λ‹ˆλ‹€( Map μ–΄λ–€ 식 μœΌλ‘œλ“  μ°Έμ—¬ν•˜κΈ° μœ„ν•΄)

참고둜 이 λˆ„μˆ˜λŠ” <string:b> λΆ€λΆ„ μ—†μ΄λŠ” λ°œμƒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

μƒμ„±ν•˜λŠ” ν•¨μˆ˜ 객체의 λΆ„ν•΄λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

>>> 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

슀크립트λ₯Ό μ•½κ°„ μ‘°μ •:

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())

Map , Rule 뿐만 μ•„λ‹ˆλΌ functools.partial 및 UnicodeConverter :

$ ./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)]

λ‹€μŒμ€ 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

이것은 μ£ΌκΈ°λ₯Ό κΉ¨κ³  λ©”λͺ¨λ¦¬ λˆ„μˆ˜λ₯Ό μ œκ±°ν•˜κΈ°μ— μΆ©λΆ„ν•©λ‹ˆλ‹€.

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):

μ‚¬μ΄ν΄μ˜ μ‹€μ œ 원인은 LOAD_CONST μžμ²΄κ°€ μƒμˆ˜κ°€ μ•„λ‹Œ 객체에 λŒ€ν•΄ μ½”λ“œ κ°μ²΄μ—μ„œ μ‚¬μš©λ˜κ³  μžˆλ‹€λŠ” 점이라고 μƒκ°ν•˜μ§€λ§Œ, 이것이 gcλ₯Ό ν˜Όλž€μŠ€λŸ½κ²Œ ν•œλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€(참쑰된 μ½”λ“œ 객체가 라이브라고 κ°€μ •ν•  λ•Œ 그듀은 μ‹€μ œλ‘œ 그렇지 μ•ŠμŠ΅λ‹ˆλ‹€)

#1524λ₯Ό 톡해

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰