1. 27 Jun, 2023 1 commit
    • Kirill Smelkov's avatar
      strconv: Add benchmarks for quote and unquote · 90f0e0ff
      Kirill Smelkov authored
      This functions are currently relatively slow. They were initially used
      in zodbdump and zodbrestore, where their speed did not matter much, but
      with bstr and ustr, since e.g. quote is used in repr, not having them to
      perform with speed similar to builtin string escaping starts to be an
      issue. Tatuya Kamada reports at nexedi/pygolang!21 (comment 170833) :
      
          ### 3. `u` seems slow with large arrays especially when `repr` it
      
          I have faced a slowness while testing `u`, `b` with python 2.7, especially with `repr`.
      
          ```python
          >>> timeit.timeit("from golang import b,u; u('あ'*199998)", number=10)
          2.02020001411438
          >>> timeit.timeit("from golang import b,u; repr(u('あ'*199998))", number=10)
          54.60263395309448
          ```
      
          `bytes`(str) is very fast.
      
          ```python
          >>> timeit.timeit("from golang import b,u; bytes('あ'*199998)", number=10)
          0.000392913818359375
          >>> timeit.timeit("from golang import b,u; repr(bytes('あ'*199998))", number=10)
          0.4604980945587158
          ```
      
          `b` is much faster than `u`, but still the repr seems slow.
      
          ```
          >>> timeit.timeit("from golang import b,u; b('あ'*199998)", number=10)
          0.0009968280792236328
          >>> timeit.timeit("from golang import b,u; repr(b('あ'*199998))", number=10)
          25.498882055282593
          ```
      
      The "repr" part of this problem is due to that both bstr.__repr__ and
      ustr.__repr__ use custom quoting routines which currently are implemented in
      pure python in strconv module:
      
      https://lab.nexedi.com/kirr/pygolang/blob/300d7dfa/golang/_golang_str.pyx#L282-291
      https://lab.nexedi.com/kirr/pygolang/blob/300d7dfa/golang/_golang_str.pyx#L582-591
      https://lab.nexedi.com/kirr/pygolang/blob/300d7dfa/golang/_golang_str.pyx#L941-970
      https://lab.nexedi.com/kirr/pygolang/blob/300d7dfa/golang/strconv.py#L31-92
      
      The fix would be to move strconv.py to Cython and to correspondingly rework it
      to avoid using python-level constructs during quoting internally.
      
      Working on that was not a priority, but soon I will need to move strconv to
      Cython for another reason: to be able to break import cycle in between _golang
      and strconv.
      
      So it makes sense to add strconv benchmark first - since we'll start moving it
      to Cython anyway - to see where we are and how further changes will help
      performance-wise.
      
      Currently we are at
      
          name                 time/op
          quote[a]              910µs ± 0%
          quote[\u03b1]        1.23ms ± 0%
          quote[\u65e5]         800µs ± 0%
          quote[\U0001f64f]    1.06ms ± 1%
          stdquote             1.17µs ± 0%
          unquote[a]           1.33ms ± 1%
          unquote[\u03b1]       952µs ± 2%
          unquote[\u65e5]       613µs ± 2%
          unquote[\U0001f64f]  3.62ms ± 1%
          stdunquote            788ns ± 0%
      
      i.e. on py2 quoting is ~ 1000x slower than builtin string escaping, and unquoting is
      even slower.
      
      on py3 the situation is better, but still not good:
      
          name                 time/op
          quote[a]              579µs ± 1%
          quote[\u03b1]         942µs ± 1%
          quote[\u65e5]         595µs ± 0%
          quote[\U0001f64f]     274µs ± 1%
          stdquote             2.70µs ± 0%
          unquote[a]            696µs ± 1%
          unquote[\u03b1]       763µs ± 0%
          unquote[\u65e5]       474µs ± 1%
          unquote[\U0001f64f]   187µs ± 0%
          stdunquote            808ns ± 0%
      
      δ(py2, py3) for the reference:
      
          name                 py2 time/op  py3 time/op  delta
          quote[a]              910µs ± 0%   579µs ± 1%   -36.42%  (p=0.008 n=5+5)
          quote[\u03b1]        1.23ms ± 0%  0.94ms ± 1%   -23.17%  (p=0.008 n=5+5)
          quote[\u65e5]         800µs ± 0%   595µs ± 0%   -25.63%  (p=0.016 n=4+5)
          quote[\U0001f64f]    1.06ms ± 1%  0.27ms ± 1%   -74.23%  (p=0.008 n=5+5)
          stdquote             1.17µs ± 0%  2.70µs ± 0%  +129.71%  (p=0.008 n=5+5)
          unquote[a]           1.33ms ± 1%  0.70ms ± 1%   -47.71%  (p=0.008 n=5+5)
          unquote[\u03b1]       952µs ± 2%   763µs ± 0%   -19.82%  (p=0.008 n=5+5)
          unquote[\u65e5]       613µs ± 2%   474µs ± 1%   -22.76%  (p=0.008 n=5+5)
          unquote[\U0001f64f]  3.62ms ± 1%  0.19ms ± 0%   -94.84%  (p=0.016 n=5+4)
          stdunquote            788ns ± 0%   808ns ± 0%    +2.59%  (p=0.016 n=4+5)
      90f0e0ff
  2. 23 Jun, 2023 3 commits
    • Kirill Smelkov's avatar
      golang_str: pybstr -> _pybstr ; pyustr -> _pyustr · baf84437
      Kirill Smelkov authored
      And let pybstr/pyustr point to version of bstr/ustr types that is actually in use:
      - when bytes/unicode are not patched -> to _pybstr/_pyustr
      - when bytes/unicode will be patched -> to bytes/unicode to where original
        _pybstr/_pyustr were copied during bytes/unicode patching.
      at runtime the code uses pybstr/pyustr instead of _pybstr/_pyustr.
      baf84437
    • Kirill Smelkov's avatar
      golang_str: Invoke bytes/unicode methods via zbytes/zunicode · 7fd58d34
      Kirill Smelkov authored
      GPython will patch builtin bytes and unicode types.
      zbytes and zunicode will refer to original unpatched types.
      We will use them to invoke original bytes/unicode methods.
      
      NOTE we will test against bytes/unicode - not zbytes/zunicode - when
      inspecting type of objects. In other words we will use original
      bytes/unicode types only to refer to their original methods and code.
      7fd58d34
    • Kirill Smelkov's avatar
      golang_str: Switch bstr/ustr to cdef classes · 4546aaec
      Kirill Smelkov authored
      For gpython to switch builtin str/unicode to bstr/ustr we will need
      bstr/ustr to have exactly the same C layout as builtin string types.
      This is possible to achieve only via `cdef class`. It is also good to
      switch to `cdef class` for RAM savings - from https://github.com/cython/cython/pull/5212#issuecomment-1387659026 :
      
          # what Cython does at runtime for `class MyBytes(bytes)`
          In [3]: MyBytes = type('MyBytes', (bytes,), {'__slots__': ()})
      
          In [4]: MyBytes
          Out[4]: __main__.MyBytes
      
          In [5]: a = bytes(b'123')
      
          In [6]: b = MyBytes(b'123')
      
          In [7]: a
          Out[7]: b'123'
      
          In [8]: b
          Out[8]: b'123'
      
          In [9]: a == b
          Out[9]: True
      
          In [10]: import sys
      
          In [11]: sys.getsizeof(a)
          Out[11]: 36
      
          In [12]: sys.getsizeof(b)
          Out[12]: 52
      
      So with `cdef class` we gain more control and optimize memory usage.
      
      This was not done before because cython forbids to `cdef class X(bytes)` due to
      https://github.com/cython/cython/issues/711. We work it around in setup.py with
      draft for proper patch pre-posted to upstream in https://github.com/cython/cython/pull/5212 .
      4546aaec
  3. 01 May, 2023 6 commits
    • Kirill Smelkov's avatar
      gpython: Fix thinko when rejecting unknown -X option · 21fab975
      Kirill Smelkov authored
      Before:
      
          (z-dev) kirr@deca:~/src/tools/go/pygolang$ gpython -X gpython.zzz
          Traceback (most recent call last):
            File "/home/kirr/src/wendelin/venv/z-dev/bin/gpython", line 3, in <module>
              from gpython import main; main()
            File "/home/kirr/src/tools/go/pygolang/gpython/__init__.py", line 397, in main
              raise RuntimeError('gpython: unknown -X option %s' % opt)
          RuntimeError: gpython: unknown -X option -X                                        <-- NOTE
      
      After:
      
          (z-dev) kirr@deca:~/src/tools/go/pygolang$ gpython -X gpython.zzz
          Traceback (most recent call last):
            File "/home/kirr/src/wendelin/venv/z-dev/bin/gpython", line 3, in <module>
              from gpython import main; main()
            File "/home/kirr/src/tools/go/pygolang/gpython/__init__.py", line 397, in main
              raise RuntimeError('gpython: unknown -X option %s' % arg)
          RuntimeError: gpython: unknown -X option gpython.zzz                               <-- NOTE
      21fab975
    • Kirill Smelkov's avatar
      gpython: Fix `gpython -X gpython.runtime=threads` to spawn subinterpreters... · 5716b130
      Kirill Smelkov authored
      gpython: Fix `gpython -X gpython.runtime=threads` to spawn subinterpreters with threads runtime by default
      
      Previously it was not the case and gpython with default being gevent
      runtime was spawned even if parent gpython was instructed to use threads runtime:
      
          (z-dev) kirr@deca:~/src/tools/go/pygolang$ gpython -X gpython.runtime=threads
          Python 2.7.18 (default, Apr 28 2021, 17:39:59)
          [GCC 10.2.1 20210110] [GPython 0.1] [threads] on linux2
          Type "help", "copyright", "credits" or "license" for more information.
          (InteractiveConsole)
          >>> import sys
          >>> sys.version
          '2.7.18 (default, Apr 28 2021, 17:39:59) \n[GCC 10.2.1 20210110] [GPython 0.1] [threads]'   <-- NOTE threads
          >>> import subprocess
          subprocess.call(sys.executable)ble)
          Python 2.7.18 (default, Apr 28 2021, 17:39:59)
          [GCC 10.2.1 20210110] [GPython 0.1] [gevent 21.1.2] on linux2                               <-- NOTE gevent
          Type "help", "copyright", "credits" or "license" for more information.
          (InteractiveConsole)
          >>>
      5716b130
    • Kirill Smelkov's avatar
      gpython: tests: Factorize test_Xruntime · e75d21fd
      Kirill Smelkov authored
      Factor-out subroutine to run tfunc in subprocess interpreter spawned with
      `-X xopt=xval`. This helps clarity and later in addition to `-X
      gpython.runtime` we will also need it to verify `-X gpython.strings`.
      e75d21fd
    • Kirill Smelkov's avatar
      golang_str: tests: Make test_strings_methods more robust with upcoming unicode=ustr · 302b51c5
      Kirill Smelkov authored
      Previously test_strings_methods was testing a method via comparing bstr
      and ustr results of .method() with similar result of unicode.method().
      This works reasonably ok. However under gpython, when unicode will be
      replaced with ustr, it will no longer compare results of bstr/ustr
      methods with something good and external - indeed in that case bstr/ustr
      .method() will be compared to result of ustr.method() which opens the
      door for bugs to stay unnoticed.
      
      -> Adjust the test to explicitly provide expected result for all entries
      in the test vector. We make sure those results are good and match std
      python because we also assert that unicode.method() matches it.
      302b51c5
    • Kirill Smelkov's avatar
      golang_str: Fix bstr.decode to handle 'string-escape' codec properly · bf16f685
      Kirill Smelkov authored
      On py2 str.decode('string-escape') returns str, not unicode and this
      property is actually being used and relied upon by Lib/pickle.py:
      
      https://github.com/python/cpython/blob/v2.7.18-0-g8d21aa21f2c/Lib/pickle.py#L967-L977
      
      We promised bstr to be drop-in replacement for str on py2, so let's
      adjust its behaviour to match the original because if we do not,
      unpickling strings will break when str is replaced by bstr under
      gpython.
      
      Do not add bstr.encode yet until we hit a real case where it is actually used.
      bf16f685
    • Kirill Smelkov's avatar
      golang_str: tests: Adjust test_strings_index2 not to depend on repr(ustr|bstr) · 9e2dab50
      Kirill Smelkov authored
      repr(ustr|bstr) will change behaviour depending on whether we are
      running under regular python, or gpython with string types replaced by
      bstr/ustr. But this test is completely orthogonal to that. -> Let's
      untie it from particular repr behaviour by emitting verified items in
      quoted form + asserting their types in the code.
      9e2dab50
  4. 30 Apr, 2023 3 commits
    • Kirill Smelkov's avatar
      fixup! golang_str: bstr/ustr pickle support · 8dc44e12
      Kirill Smelkov authored
      In ebd18f3f the code was ok but there is a thinko in test: it needs to
      test all pickle protocols from 0 to _including_ HIGHEST_PROTOCOL.
      8dc44e12
    • Kirill Smelkov's avatar
      golang_str: fix UCS2 builds · 3f6bc5c6
      Kirill Smelkov authored
          + ./trun python -m pytest -vvsx golang/golang_str_test.py
          ==================================== test session starts =====================================
          platform linux2 -- Python 2.7.18, pytest-4.6.11, py-1.11.0, pluggy-0.13.1 -- /home/kirr/src/tools/go/py2d.venv2023/bin/python
          cachedir: .pytest_cache
          rootdir: /home/kirr/src/tools/go/pygolang-xgpystr
          collected 64 items
      
          golang/golang_str_test.py::test_strings_basic Traceback (most recent call last):
            File "golang/_golang_str.pyx", line 2270, in golang._golang._xuniord
              return ord(u)
          ValueError: only single character unicode strings can be converted to Py_UCS4, got length 2
          Exception ValueError: 'only single character unicode strings can be converted to Py_UCS4, got length 2' in 'golang._golang._utf8_decode_rune' ignored
      
          (py2d.venv2023) kirr@deca:~/src/tools/go/pygolang-xgpystr$ python
          Python 2.7.18 (tags/2.7-dirty:8d21aa21f2c, Mar 30 2023, 07:38:40)
          [GCC 10.2.1 20210110] on linux2
          Type "help", "copyright", "credits" or "license" for more information.
          >>> from pygolang import *
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          ImportError: No module named pygolang
          >>> from golang import *
          >>> ord('xy')
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          TypeError: ord() expected a character, but string of length 2 found
          >>> ord(b'xy')
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          TypeError: ord() expected a character, but string of length 2 found
          >>> ord(u'xy')
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          TypeError: ord() expected a character, but string of length 2 found
          >>> ord(b('xy'))
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
          TypeError: ord() expected a character, but string of length 2 found
          >>> ord(u('xy'))
          Traceback (most recent call last):
            File "golang/_golang_str.pyx", line 2270, in golang._golang._xuniord
              return ord(u)
          ValueError: only single character unicode strings can be converted to Py_UCS4, got length 2
          Exception ValueError: 'only single character unicode strings can be converted to Py_UCS4, got length 2' in 'golang._golang._utf8_decode_rune' ignored
          Traceback (most recent call last):
            File "<stdin>", line 1, in <module>
            File "golang/_golang_str.pyx", line 157, in golang._golang.pyu
              us = _pyu(pyustr, s)
            File "golang/_golang_str.pyx", line 195, in golang._golang._pyu
              s = _utf8_decode_surrogateescape(s)
            File "golang/_golang_str.pyx", line 2198, in golang._golang._utf8_decode_surrogateescape
              emit(_xunichr(r))
            File "golang/_golang_str.pyx", line 2286, in golang._golang._xunichr
              return unichr(0xd800 + (uh >> 10)) + \
          ValueError: unichr() arg not in range(0x10000) (narrow Python build)
      
      It was broken in 50b8cb7e (strconv: Move functionality related to UTF8 encode/decode into _golang_str)
      3f6bc5c6
    • Kirill Smelkov's avatar
      Sync with master · 2b56ce71
      Kirill Smelkov authored
      2b56ce71
  5. 27 Apr, 2023 27 commits
    • Kirill Smelkov's avatar
      Windows support · 55d39d4d
      Kirill Smelkov authored
      Pygolang stopped to work on Windows in 2019 starting from 8fa3c15b (Start using
      Cython and providing Cython/nogil API). Restore it now.
      55d39d4d
    • Kirill Smelkov's avatar
      setup: Add classifiers to indicate Pygolang works on all Linux / macOS and Windows · d1e92fa2
      Kirill Smelkov authored
      Linux and macOS was working before. And we just added Windows support in
      the previous patches.
      d1e92fa2
    • Kirill Smelkov's avatar
      libgolang/gevent: Fix io_read deadlock and io_read/io_write potential data corruption · 3c10a0a3
      Kirill Smelkov authored
      When underlying pygfobj is FileObjectThread its .readinto() leads to
      deadlock because it is no cooperative(*). This manifests as
      test_pyx_os_pipe_cpp hang when run by gpython on windows.
      
      -> Workaround this by reading first into intermediate buffer and then
      copying data to buffer that user provided.
      
      After this the deadlock is gone but test_pyx_os_pipe_cpp starts to fail
      and crash randomly. That's because similarly to channels we need to
      care and not access a buffer if it is located on stack and owning
      greenlet is inactive. Because when a greenlet is inactive, its stack is
      reused by another active greenlet and writing/reading to on-stack memory
      accesses that second greenlet stack, corrupting it on write.
      
      -> do the same what we do in chan operations: use intermediate on-heap
      buffer to protect original user's buffer to be accesses because it might
      be located on stack. That's what actually happens in
      test_pyx_os_pipe_cpp where two goroutines read and write to each other
      via pipe and using on-stack located buffers. And becuase on windows
      pipes, like regular files, are wrapped with FileObjectThread, when
      reading greenlet becomes suspended waiting for read reasul, it will be
      another greenlet to run on its stack, write to another end of a pipe,
      wakeup IO thread, which will write the data to requested buffer on G1
      stack and oops - it was G2 there.
      
      (*) see https://github.com/gevent/gevent/pull/1948 for details
      3c10a0a3
    • Kirill Smelkov's avatar
      gpython: tests: Don't assume path delimiter is / · d9505256
      Kirill Smelkov authored
      Because on Windows it is \ and using / for it to scan in path fails and
      leads to test_pymain_opt failure:
      
           E             Full diff:
           E               [
           E                'sys.flags.debug:      0',
           E                'sys.flags.optimize:   0',
           E                '__debug__:            True',
           E                'assert:               True',
           E                'docstrings:           True',
           E                'import mod.py:        '
           E             -  'C:\\users\\kirr\\Temp\\modpy_imports_fromvmgs8go4\\__pycache__/mod.cpython-310.pyc',
           E             ?                                            ^^  ----
           E             +  'C:\\users\\kirr\\Temp\\modpy_imports_from850gb81s\\__pycache__/mod.cpython-310.pyc',
           E             ?                                            ^^^ +++
           E               ]
      
      Here the difference is because gpython/testprog/print_opt.py failed to
      detect tmpd prefix and strip it.
      d9505256
    • Kirill Smelkov's avatar
      gpython: tests: Prepare expected repr properly · 161629e6
      Kirill Smelkov authored
      In several places we were preparing repr of a string as '%s' which works
      ok most of the time but leads to failure if string contains '\'
      characters e.g. if it represents a path and we are running on windows:
      
              def test_pymain():
                  from golang import b
      
                  ....
      
                  # -m <module>
                  _ = pyout(['-m', 'hello', 'abc', 'def'], cwd=testdata)
                  # realpath rewrites e.g. `local/lib -> lib` if local/lib is symlink
                  hellopy = realpath(join(testdata, 'hello.py'))
          >       assert _ == b"hello\nworld\n['%s', 'abc', 'def']\n" % b(hellopy)
          E       assert b"hello\nworld\n['Z:\\\\home\\\\kirr\\\\src\\\\tools\\\\go\\\\pygo-win\\\\pygolang\\\\gpython\\\\testdata\\\\hello.py', 'abc', 'def'
          ]\n" == b"hello\nworld\n['Z:\\home\\kirr\\src\\tools\\go\\pygo-win\\pygolang\\gpython\\testdata\\hello.py', 'abc', 'def']\n"
          E         At index 17 diff: b'\\' != b'h'
          E         Full diff:
          E           (
          E         -  b"hello\nworld\n['Z:\\home\\kirr\\src\\tools\\go\\pygo-win\\pygolang\\gpytho"
          E         ?                                                                  ------------
          E         +  b"hello\nworld\n['Z:\\\\home\\\\kirr\\\\src\\\\tools\\\\go\\\\pygo-win\\\\pygo"
          E         ?                        ++    ++        ++   ++         ++  ++            ++
          E         -  b"n\\testdata\\hello.py', 'abc', 'def']\n",
          E         +  b"lang\\\\gpython\\\\testdata\\\\hello.py', 'abc', 'def']\n",
          E         ?    ++ ++++++++++++++            ++
          E           )
      
          gpython\gpython_test.py:183: AssertionError
      
      -> Fix it by preparing repr via repr(...) properly.
      161629e6
    • Kirill Smelkov's avatar
      gpython: Fix gevent activation on Windows · f23a9ef5
      Kirill Smelkov authored
      On Windows if geventmp is present gpython startup fails:
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang>gpython
          Traceback (most recent call last):
            File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
              return _run_code(code, main_globals, None,
            File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
              exec(code, run_globals)
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\Scripts\gpython.exe\__main__.py", line 7, in <module>
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 450, in main
              pymain(argv, init)
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 223, in pymain
              init()
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 428, in init
              _ = monkey.patch_all(thread=patch_thread)      # XXX sys=True ?
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\gevent\monkey.py", line 1255, in patch_all
              _notify_patch(events.GeventWillPatchAllEvent(modules_to_patch, kwargs), _warnings)
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\gevent\monkey.py", line 190, in _notify_patch
              notify_and_call_entry_points(event)
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\gevent\events.py", line 105, in notify_and_call_entry_points
              subscriber(event)
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\geventmp\monkey.py", line 160, in _patch_mp
              _patch_module("_mp.3._mp_util", _patch_module=True, _package_prefix='geventmp.')
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\geventmp\monkey.py", line 121, in _patch_module
              gevent_module = import_module(_package_prefix + name)
            File "C:\Program Files\Python310\lib\importlib\__init__.py", line 126, in import_module
              return _bootstrap._gcd_import(name[level:], package, level)
            File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
            File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
            File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
            File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
            File "<frozen importlib._bootstrap_external>", line 883, in exec_module
            File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\geventmp\_mp\3\_mp_util.py", line 16, in <module>
              from gevent.os import _watch_child
          ImportError: cannot import name '_watch_child' from 'gevent.os' (Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages\gevent\os.py)
      
      That happens because geventmp does not support windows actually.
      
      -> Fix it by requiring geventmp to be present only on non-windows.
      
      Adjust related comment as https://github.com/karellen/geventmp/pull/2 has been merged long ago.
      f23a9ef5
    • Kirill Smelkov's avatar
      gpython: Fix startup on Windows when installed by pip · 377e44cb
      Kirill Smelkov authored
      On windows setuptools install gpython.exe and gpython-script.py while
      pip/distlib install gpython.exe with gpython-script sometimes embedded
      into gpython.exe itself with argv[0] pointing to 'gpython' without .exe
      suffix. This leads to gpython startup failure:
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang>gpython
          Traceback (most recent call last):
            File "C:\Program Files\Python310\lib\runpy.py", line 196, in _run_module_as_main
              return _run_code(code, main_globals, None,
            File "C:\Program Files\Python310\lib\runpy.py", line 86, in _run_code
              exec(code, run_globals)
            File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\Scripts\gpython.exe\__main__.py", line 7, in <module>
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 437, in main
              pymain(argv, init)
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 79, in pymain
              if not _is_buildout_script(exe):
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\gpython\__init__.py", line 442, in _is_buildout_script
              with open(path, 'rb') as f:
          FileNotFoundError: [Errno 2] No such file or directory: 'Z:\\home\\kirr\\src\\tools\\go\\pygo-win\\1.wenv\\Scripts\\gpython'
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang>dir ../1.wenv/scripts/gpython*
      
          Directory of Z:\home\kirr\src\tools\go\pygo-win\1.wenv\scripts
      
          26.04.2023     14:17       108,404  gpython.exe
                 1 file                   108,404 bytes
                 0 directories     88,508,866,560 bytes free
      
      -> Adjust pymain to handle this case accordingly.
      377e44cb
    • Kirill Smelkov's avatar
      os/signal: tests: Adjust exit code expectation for Windows · 2f632a3e
      Kirill Smelkov authored
      On windows upon terminating signal reception exict code of the process
      is always 3 instead of -signo. And so test_signal_all was failing as
      
              def test_signal_all():
                  retcode, out, _ = _pyrun([dir_testprog + "/signal_test_all.py"], stdout=PIPE)
                  assert b"ok (notify)"        in out
                  assert b"ok (ignore)"        in out
                  assert b"terminating ..."    in out
          >       assert retcode == -syscall.SIGTERM.signo
          E       assert 3 == -15
          E        +  where 15 = os.Signal(15).signo
          E        +    where os.Signal(15) = syscall.SIGTERM
      2f632a3e
    • Kirill Smelkov's avatar
      os/signal: Adjust signal_test_all.py to support Windows · c1d5b67b
      Kirill Smelkov authored
      - There is no e.g. signal.SIGKILL on windows:
      
          golang/os/signal_test.py::test_signal_all Traceback (most recent call last):
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\os\testprog\signal_test_all.py", line 82, in <module>
              main()
            File "Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\os\testprog\signal_test_all.py", line 39, in main
              allsigv.remove(syscall.SIGKILL) # SIGKILL/SIGSTOP cannot be caught
          AttributeError: module 'golang.syscall' has no attribute 'SIGKILL'. Did you mean: 'SIGILL'?
      
        -> filter-out signals conditionally depending on their presence.
      
      - Need to also filter-out SIGILL/SIGABRT because upon reception MSVC
        runtime terminates process.
      c1d5b67b
    • Kirill Smelkov's avatar
      os/signal: tests: use raise instead of kill(self) · ca4af748
      Kirill Smelkov authored
      Because on Windows os.kill unconditionally terminates target process,
      even if own self, instead of sending it any kind of signal.
      ca4af748
    • Kirill Smelkov's avatar
      os/signal: tests: Use SIGTERM/SIGINT on Windows · 75d40910
      Kirill Smelkov authored
      We use SIGUSR1/SIGUSR2 mainly in this test, but those signals are not
      available on Windows:
      
              @func
              def test_signal():
                  # Notify/Stop with wrong chan dtype -> panic
                  _ = panics("pychan: channel type mismatch")
          >       with _:  signal.Notify(chan(2), syscall.SIGUSR1)
          E       AttributeError: module 'golang.syscall' has no attribute 'SIGUSR1'
      
      -> Use SIGTERM/SIGINT if SIGUSR1/SIGUSR2 are not available.
      
      Don't want to use SIGTERM/SIGINT unconditionally because those signals are
      better to leave for the job control so that e.g. nxdtest can properly kill
      spawned pygolang tests.
      75d40910
    • Kirill Smelkov's avatar
      *.py: Open files in binary mode and decode to UTF-8 explicitly if needed · a5349f5d
      Kirill Smelkov authored
      On Windows in text mode files are opened with encoding=locale.getdefaultlocale()
      which is CP125X instead of UTF-8 even if $PYTHONIOENCODING=UTF-8. This
      way e.g. test_strings_print fail as:
      
          E           Failed: not equal:
          E           Expected:
          E               print(qq(b)): "привет αβγ b"
          E               print(qq(u)): "привет αβγ u"
          E           Got:
          E               print(qq(b)): "привет αβγ b"
          E               print(qq(u)): "привет αβγ u"
      
      where "Expected" was read from golang/testprog/golang_test_str.txt and
      decoded wrongly.
      
      -> Fix it by always opening files for reading in binary mode and
      utf8-decoding manually, if needed, everywhere.
      a5349f5d
    • Kirill Smelkov's avatar
      golang: tests: Spawn subprocess python with $PYTHONIOENCODING set to encoding... · 8d723b34
      Kirill Smelkov authored
      golang: tests: Spawn subprocess python with $PYTHONIOENCODING set to encoding of stdin/stdout/stderr
      
      We need to do it because on Windows `python x.py | ...` runs with stdio
      encoding set to cp125X even if just `python x.py` runs with stdio
      encoding=UTF-8.
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\testprog>python golang_test_str.py
          print(qq(b)): "привет b"
          print(qq(u)): "привет u"
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\testprog>python golang_test_str.py |more
          print(qq(b)): "яЁштхЄ b"
          print(qq(u)): "яЁштхЄ u"
      
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\testprog>python golang_test_str.py >aaa.txt
          (1.wenv) Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\testprog>type aaa.txt
          print(qq(b)): "яЁштхЄ b"
          print(qq(u)): "яЁштхЄ u"
      
      Which leads to the following test_strings_print failure:
      
          E           Failed: not equal:
          E           Expected:
          E               print(qq(b)): "привет b"
          E               print(qq(u)): "привет u"
          E           Got:
          E               print(qq(b)): "������ b"
          E               print(qq(u)): "������ u"
      
      We also change golang_test_str.py to print not only russian and english
      letters, but also greek ones. This is to make sure that stdout IO encoding would
      not go cp125X unnoticed because then printing will fail with UnicodeEncodeError.
      
      Note: in the above example both got and expected are wrong. Via
      $PYTHONIOENCODING we only fix "got" and we will fix "expected" in the followup
      patch. After current patch test_strings_print result is
      
          E           Failed: not equal:
          E           Expected:
          E               print(qq(b)): "привет αβγ b"
          E               print(qq(u)): "привет αβγ u"
          E           Got:
          E               print(qq(b)): "привет αβγ b"
          E               print(qq(u)): "привет αβγ u"
      8d723b34
    • Kirill Smelkov's avatar
      golang: tests: Adjust golang_test_defer_excchain.txt-pytest golden to support Windows · ee55e19d
      Kirill Smelkov authored
      Apparently on Windows pytest detects terminal width differently than on
      Linux/macOS and the test fails as
      
          E           Failed: not equal:
          E           Differences (unified diff with -expected +actual):
          E               @@ -1,5 +1,12 @@
          E               -...
          E               -_____________________________________ main _____________________________________
          E               -../__init__.py:...: in _
          E               +============================= test session starts =============================
          E               +platform win32 -- Python 3.10.11, pytest-7.3.1, pluggy-1.0.0
          E               +rootdir: PYGOLANG
          E               +collected 1 item
          E               +<BLANKLINE>
          E               +golang_test_defer_excchain.py F                                          [100%]
          E               +<BLANKLINE>
          E               +================================== FAILURES ===================================
          E               +____________________________________ main _____________________________________
          E               +../__init__.py:106: in _
          E                    return f(*argv, **kw)
          E                golang_test_defer_excchain.py:42: in main
          E               @@ -8,6 +15,6 @@
          E                <BLANKLINE>
          E                During handling of the above exception, another exception occurred:
          E               -../__init__.py:...: in __exit__
          E               -    ...
          E               +../__init__.py:183: in __exit__
          E               +    d()
          E                golang_test_defer_excchain.py:31: in d1
          E                    raise RuntimeError("d1: aaa")
          E               @@ -15,9 +22,9 @@
          E                <BLANKLINE>
          E                During handling of the above exception, another exception occurred:
          E               -../__init__.py:...: in __exit__
          E               -    ...
          E               +../__init__.py:183: in __exit__
          E               +    d()
          E                golang_test_defer_excchain.py:33: in d2
          E                    1/0
          E               -E   ZeroDivisionError: ...
          E               +E   ZeroDivisionError: division by zero
          E                <BLANKLINE>
          E                During handling of the above exception, another exception occurred:
          E               @@ -25,3 +32,5 @@
          E                    raise RuntimeError("d3: bbb")
          E                E   RuntimeError: d3: bbb
          E               -=========================== ...
          E               +=========================== short test summary info ===========================
          E               +FAILED golang_test_defer_excchain.py::main - RuntimeError: d3: bbb
          E               +============================== 1 failed in 1.45s ==============================
      
      -> Fix it by not requesting header to be of particular width.
      ee55e19d
    • Kirill Smelkov's avatar
      golang: tests: Normalize \r\n to \n in doctests on windows · 17c1d2fa
      Kirill Smelkov authored
      On windows print emits \r\n instead of just \n. Here is how e.g.
      test_defer_excchain_dump fails without normalization:
      
          E           Failed: not equal:
          E           Differences (unified diff with -expected +actual):
          E               @@ -1,42 +1,43 @@
          E               -Traceback (most recent call last):
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in _
          E               -    return f(*argv, **kw)
          E               -  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 42, in main
          E               -    raise RuntimeError("err")
          E               -RuntimeError: err
          E               -<BLANKLINE>
          E               -During handling of the above exception, another exception occurred:
          E               -<BLANKLINE>
          E               -Traceback (most recent call last):
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in __exit__
          E               -    ...
          E               -  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 31, in d1
          E               -    raise RuntimeError("d1: aaa")
          E               -RuntimeError: d1: aaa
          E               -<BLANKLINE>
          E               -During handling of the above exception, another exception occurred:
          E               -<BLANKLINE>
          E               -Traceback (most recent call last):
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in __exit__
          E               -    ...
          E               -  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 33, in d2
          E               -    1/0
          E               -ZeroDivisionError: ...
          E               -<BLANKLINE>
          E               -During handling of the above exception, another exception occurred:
          E               -<BLANKLINE>
          E               -Traceback (most recent call last):
          E               -  ... "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 45, in <module>
          E               -    main()
          E               -  ...
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in _
          E               -    with __goframe__:
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in __exit__
          E               -    ...
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in __exit__
          E               -    ...
          E               -  File "PYGOLANG/golang/__init__.py", line ..., in __exit__
          E               -    ...
          E               -  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 35, in d3
          E               -    raise RuntimeError("d3: bbb")
          E               -RuntimeError: d3: bbb
          E               +Traceback (most recent call last):
          E               +  File "PYGOLANG/golang/__init__.py", line 106, in _
          E               +    return f(*argv, **kw)
          E               +  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 42, in main
          E               +    raise RuntimeError("err")
          E               +RuntimeError: err
          E               +
          E               +During handling of the above exception, another exception occurred:
          E               +
          E               +Traceback (most recent call last):
          E               +  File "PYGOLANG/golang/__init__.py", line 183, in __exit__
          E               +    d()
          E               +  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 31, in d1
          E               +    raise RuntimeError("d1: aaa")
          E               +RuntimeError: d1: aaa
          E               +
          E               +During handling of the above exception, another exception occurred:
          E               +
          E               +Traceback (most recent call last):
          E               +  File "PYGOLANG/golang/__init__.py", line 183, in __exit__
          E               +    d()
          E               +  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 33, in d2
          E               +    1/0
          E               +ZeroDivisionError: division by zero
          E               +
          E               +During handling of the above exception, another exception occurred:
          E               +
          E               +Traceback (most recent call last):
          E               +  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 45, in <module>
          E               +    main()
          E               +  File "Z:\home\kirr\src\tools\go\pygo-win\1.wenv\lib\site-packages/decorator.py", line 232, in fun
          E               +    return caller(func, *(extras + args), **kw)
          E               +  File "PYGOLANG/golang/__init__.py", line 105, in _
          E               +    with __goframe__:
          E               +  File "PYGOLANG/golang/__init__.py", line 182, in __exit__
          E               +    with __goframe__:
          E               +  File "PYGOLANG/golang/__init__.py", line 182, in __exit__
          E               +    with __goframe__:
          E               +  File "PYGOLANG/golang/__init__.py", line 183, in __exit__
          E               +    d()
          E               +  File "PYGOLANG/golang/testprog/golang_test_defer_excchain.py", line 35, in d3
          E               +    raise RuntimeError("d3: bbb")
          E               +RuntimeError: d3: bbb
      17c1d2fa
    • Kirill Smelkov's avatar
      golang: tests: Normalize paths to use / delimiter in doctests's got · 06935819
      Kirill Smelkov authored
      Else on Windows e.g. test_defer_excchain_traceback fails as
      
          E           Failed: not equal:
          E           Differences (unified diff with -expected +actual):
          E               @@ -1,8 +1,8 @@
          E                Traceback (most recent call last):
          E               -  File "PYGOLANG/golang/golang_test.py", line ..., in test_defer_excchain_traceback
          E               +  File "PYGOLANG\golang\golang_test.py", line 1524, in test_defer_excchain_traceback
          E                    alpha()
          E               -  File "PYGOLANG/golang/golang_test.py", line ..., in alpha
          E               +  File "PYGOLANG\golang\golang_test.py", line 1521, in alpha
          E                    beta()
          E               -  File "PYGOLANG/golang/golang_test.py", line ..., in beta
          E               +  File "PYGOLANG\golang\golang_test.py", line 1520, in beta
          E                    raise RuntimeError("gamma")
          E                RuntimeError: gamma
      06935819
    • Kirill Smelkov's avatar
      golang: Prepare path for libgolang.dll before importing _golang · a5ce8175
      Kirill Smelkov authored
      Else it fails to import on Windows:
      
          collecting ... 05c8:err:module:import_dll Library libgolang.dll (which is needed by L"Z:\\home\\kirr\\src\\tools\\go\\pygo-win\\pygolang\\golang\\_golang.cp310-win_amd64.pyd") not found
          collected 0 items / 1 error
      
          =========================================================== ERRORS ===========================================================
          ___________________________________________ ERROR collecting golang/golang_test.py ___________________________________
          ImportError while importing test module 'Z:\home\kirr\src\tools\go\pygo-win\pygolang\golang\golang_test.py'.
          Hint: make sure your test modules/packages have valid Python names.
          Traceback:
          C:\Program Files\Python310\lib\importlib\__init__.py:126: in import_module
              return _bootstrap._gcd_import(name[level:], package, level)
          golang\__init__.py:45: in <module>
              from golang._golang import _pysys_exc_clear as _sys_exc_clear
          E   ImportError: DLL load failed while importing _golang: Модуль не найден.
      
      We need to increase required setuptools_dso version because dylink_prepare_dso
      and generation of *_dsoinfo.py modules was done after setuptools_dso 2.
      a5ce8175
    • Kirill Smelkov's avatar
      .gitignore += *.lib *.exp · 0b97e0a8
      Kirill Smelkov authored
      On Windows this files are generated when linking alongside *.dll files
      if there are exported symbols.
      
      See also https://github.com/mdavidsaver/setuptools_dso/pull/25.
      0b97e0a8
    • Kirill Smelkov's avatar
      setup: We need to link libpyxruntime DSO to py runtime · fbed01b0
      Kirill Smelkov authored
      Libpyxruntime by definition contains python-specific code and we already
      compile it with include path having python includes in it. However on
      windows pyconfig.h contains
      
          #pragma comment(lib,"python3.lib")
      
      which instructs the linker to automatically link to that library. And
      without proper library path the link fails:
      
          Z:\home\kirr\src\tools\go\pygo-win\BuildTools\vc\tools\msvc\14.35.32215\bin\Hostx64\x64\link.exe /nologo /INCREMENTAL:NO /LTCG /DLL /MANIFEST:EMBED,ID=2 /MANIFESTUAC:NO /LIBPATH:build\lib.win-amd64-cpython-31
          0\golang\runtime /LIBPATH:z:\home\kirr\src\tools\go\pygo-win\BuildTools\vc\tools\msvc\14.35.32215\lib\x64 /LIBPATH:z:\home\kirr\src\tools\go\pygo-win\BuildTools\kits\10\lib\10.0.22000.0\ucrt\x64 /LIBPATH:z:\h
          ome\kirr\src\tools\go\pygo-win\BuildTools\kits\10\lib\10.0.22000.0\um\x64 libgolang.lib build\temp.win-amd64-cpython-310\Release\golang/runtime/libpyxruntime.obj /OUT:build\lib.win-amd64-cpython-310\golang\ru
          ntime\libpyxruntime.dll /IMPLIB:build\lib.win-amd64-cpython-310\golang\runtime\libpyxruntime.lib
          LINK : fatal error LNK1104: cannot open file 'python310.lib'
      
      -> Fix it by providing proper library path for python.dll .
      
      We need to do it ourselves only for libpyxruntime dso because for py
      extensions this is automatically done by distutils out of the box.
      fbed01b0
    • Kirill Smelkov's avatar
      libgolang/gevent: Adjust to support Windows · 437bbd56
      Kirill Smelkov authored
      On Windows rthere is no fcntl
      
          golang/runtime\_runtime_gevent.cpp(4811): error C3861: 'S_ISBLK': identifier not found
          golang/runtime\_runtime_gevent.cpp(4823): error C2039: 'Fcntl': is not a member of 'golang::internal::syscall'
          .\golang/runtime/internal/syscall.h(36): note: see declaration of 'golang::internal::syscall'
          golang/runtime\_runtime_gevent.cpp(4823): error C2065: 'F_GETFL': undeclared identifier
          golang/runtime\_runtime_gevent.cpp(4823): error C3861: 'Fcntl': identifier not found
          golang/runtime\_runtime_gevent.cpp(4870): error C2065: 'O_ACCMODE': undeclared identifier
          golang/runtime\_runtime_gevent.cpp(4889): error C2039: 'Fcntl': is not a member of 'golang::internal::syscall'
          .\golang/runtime/internal/syscall.h(36): note: see declaration of 'golang::internal::syscall'
          golang/runtime\_runtime_gevent.cpp(4889): error C2065: 'F_SETFL': undeclared identifier
          golang/runtime\_runtime_gevent.cpp(4889): error C2065: 'O_NONBLOCK': undeclared identifier
          golang/runtime\_runtime_gevent.cpp(4889): error C3861: 'Fcntl': identifier not found
      
      and even if there would be gevent does not provide cooperative version
      of FileObject for non-POSIX platforms.
      
      -> Always use FileObjectThread on windows even for pipes.
      437bbd56
    • Kirill Smelkov's avatar
      os/signal: Use windows-specific code to turn wakeup pipe into nonblocking mode · 45473979
      Kirill Smelkov authored
      There is no fcntl on windows. Named pipes can be set into nonblocking
      mode via SetNamedPipeHandleState(PIPE_NOWAIT).
      45473979
    • Kirill Smelkov's avatar
      os/signal: Internally emulate sigaction via signal · acc6c3f1
      Kirill Smelkov authored
      On MSVC there is no sigaction and we have to use signal. Fortunately
      our sigaction usage in os/signal can be practically emulated because we
      do not use all features of sigaction and because on MSVC there is
      SIG_GET signal extension to query current signal handler without
      resetting it.
      
      Because the emulation is limited we keep it in os/signal.cpp instead of
      adjusting syscall::Sigaction .
      acc6c3f1
    • Kirill Smelkov's avatar
      os/signal: Factor call to sys::Sigaction into sys_sigaction internal helper · b15fa818
      Kirill Smelkov authored
      On MSVC we will need to use signal(2), because there is no sigaction,
      and sigaction will be partly emulated through that. Move signal setting
      code into common place as a preparatory step.
      b15fa818
    • Kirill Smelkov's avatar
      internal/syscall: Initial adjust to support MSVC · d3bf3b7d
      Kirill Smelkov authored
      - there is no strerror_r, but strerror_s instead
      - fcntl is not supported
      - we need to explicitly tell open to open files in binary mode
      - pipes are created via _pipe and explicitly in binary mode
      - there is no sigaction.
      d3bf3b7d
    • Kirill Smelkov's avatar
      internal/syscall: Adjust Pipe to take flags · 63b5c1d9
      Kirill Smelkov authored
      This way e.g. O_CLOEXEC can be passed in at pipe creation time removing
      potential race in between pipe+fcntl vs exec in the middle. It will also
      help in windows porting in follow-up patches becuase there is no fcntl
      on Windows, but pipe with O_CLOEXEC semantic can be created directly
      with flags=_O_NOINHERIT.
      63b5c1d9
    • Kirill Smelkov's avatar
      internal/atomic: Adjust to support MSVC · 534033a5
      Kirill Smelkov authored
      There is no fork on windows and that we do not need to install atfork
      handler at all.
      
          golang/runtime/internal/atomic.cpp(23): fatal error C1083: Cannot open include file: 'pthread.h': No such file or directory
      534033a5
    • Kirill Smelkov's avatar
      fmt: Adjust to support MSVC · 5098decb
      Kirill Smelkov authored
      - there is no __attribute__ on that compiler
      - sadly va_start is rejected to work on reference:
      
          z:\home\kirr\src\tools\go\pygo-win\BuildTools\vc\tools\msvc\14.35.32215\include\vadefs.h(194): error C2338: static_assert failed: 'va_start argument must not have reference type and must not be parenthesized'
          golang/fmt.cpp(53): note: see reference to class template instantiation '__vcrt_assert_va_start_is_not_reference<const golang::string &>' being compiled
      
        -> change format from string& to be string in sprintf.
      
        Probably it should be ok in practice from performance point of view because
        string contains only pointer to data, not the data itself.
      5098decb