Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gevent
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gevent
Commits
9b4085b5
Commit
9b4085b5
authored
Dec 19, 2019
by
Jason Madden
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix TestPatchedTPE.test__future.
parent
894fb643
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
199 additions
and
163 deletions
+199
-163
src/gevent/_fileobjectcommon.py
src/gevent/_fileobjectcommon.py
+5
-3
src/gevent/_hub_local.py
src/gevent/_hub_local.py
+6
-6
src/gevent/hub.py
src/gevent/hub.py
+21
-12
src/gevent/testing/__init__.py
src/gevent/testing/__init__.py
+7
-6
src/gevent/testing/hub.py
src/gevent/testing/hub.py
+9
-3
src/gevent/tests/monkey_package/threadpool_monkey_patches.py
src/gevent/tests/monkey_package/threadpool_monkey_patches.py
+2
-0
src/gevent/tests/test__close_backend_fd.py
src/gevent/tests/test__close_backend_fd.py
+5
-4
src/gevent/tests/test__monkey_module_run.py
src/gevent/tests/test__monkey_module_run.py
+1
-1
src/gevent/tests/test__threadpool.py
src/gevent/tests/test__threadpool.py
+127
-111
src/gevent/tests/test__threadpool_executor_patched.py
src/gevent/tests/test__threadpool_executor_patched.py
+6
-7
src/gevent/threadpool.py
src/gevent/threadpool.py
+10
-10
No files found.
src/gevent/_fileobjectcommon.py
View file @
9b4085b5
...
...
@@ -142,9 +142,11 @@ class OpenDescriptor(object): # pylint:disable=too-many-instance-attributes
if
universal
:
if
can_write
:
raise
ValueError
(
"mode U cannot be combined with 'x', 'w', 'a', or '+'"
)
import
warnings
warnings
.
warn
(
"'U' mode is deprecated"
,
DeprecationWarning
,
4
)
# Just because the stdlib deprecates this, no need for us to do so as well.
# Especially not while we still support Python 2.
# import warnings
# warnings.warn("'U' mode is deprecated",
# DeprecationWarning, 4)
reading
=
True
if
text
and
binary
:
raise
ValueError
(
"can't have text and binary mode at once"
)
...
...
src/gevent/_hub_local.py
View file @
9b4085b5
...
...
@@ -51,7 +51,7 @@ def set_default_hub_class(hubtype):
global
Hub
Hub
=
hubtype
def
get_hub
(
*
args
,
**
kwargs
):
def
get_hub
(
*
args
,
**
kwargs
):
# pylint:disable=unused-argument
"""
Return the hub for the current thread.
...
...
@@ -63,12 +63,12 @@ def get_hub(*args, **kwargs):
only used when the hub was created, and so were non-deterministic---to be
sure they were used, *all* callers had to pass them, or they were order-dependent.
Use ``set_hub`` instead.
.. versionchanged:: 1.5a3
The *args* and *kwargs* arguments are now completely ignored.
"""
hub
=
_threadlocal
.
hub
if
hub
is
None
:
hubtype
=
get_hub_class
()
hub
=
_threadlocal
.
hub
=
hubtype
(
*
args
,
**
kwargs
)
return
hub
return
get_hub_noargs
()
def
get_hub_noargs
():
# Just like get_hub, but cheaper to call because it
...
...
src/gevent/hub.py
View file @
9b4085b5
...
...
@@ -460,6 +460,19 @@ class Hub(WaitOperationsGreenlet):
result
+=
' thread_ident=%s'
%
(
hex
(
self
.
thread_ident
),
)
return
result
+
'>'
def
_normalize_exception
(
self
,
t
,
v
,
tb
):
# Allow passing in all None if the caller doesn't have
# easy access to sys.exc_info()
if
(
t
,
v
,
tb
)
==
(
None
,
None
,
None
):
t
,
v
,
tb
=
sys
.
exc_info
()
if
isinstance
(
v
,
str
):
# Cython can raise errors where the value is a plain string
# e.g., AttributeError, "_semaphore.Semaphore has no attr", <traceback>
v
=
t
(
v
)
return
t
,
v
,
tb
def
handle_error
(
self
,
context
,
type
,
value
,
tb
):
"""
Called by the event loop when an error occurs. The default
...
...
@@ -486,13 +499,7 @@ class Hub(WaitOperationsGreenlet):
that should generally result in exiting the loop and being
thrown to the parent greenlet.
"""
if
(
type
,
value
,
tb
)
==
(
None
,
None
,
None
):
type
,
value
,
tb
=
sys
.
exc_info
()
if
isinstance
(
value
,
str
):
# Cython can raise errors where the value is a plain string
# e.g., AttributeError, "_semaphore.Semaphore has no attr", <traceback>
value
=
type
(
value
)
type
,
value
,
tb
=
self
.
_normalize_exception
(
type
,
value
,
tb
)
if
not
issubclass
(
type
,
self
.
NOT_ERROR
):
self
.
print_exception
(
context
,
type
,
value
,
tb
)
...
...
@@ -543,7 +550,7 @@ class Hub(WaitOperationsGreenlet):
stderr
=
stderr
.
io
# pylint:disable=no-member
return
stderr
def
print_exception
(
self
,
context
,
t
ype
,
value
,
tb
):
def
print_exception
(
self
,
context
,
t
,
v
,
tb
):
# Python 3 does not gracefully handle None value or tb in
# traceback.print_exception() as previous versions did.
# pylint:disable=no-member
...
...
@@ -555,10 +562,12 @@ class Hub(WaitOperationsGreenlet):
# See https://github.com/gevent/gevent/issues/1295
return
if
value
is
None
:
errstream
.
write
(
'%s
\
n
'
%
type
.
__name__
)
t
,
v
,
tb
=
self
.
_normalize_exception
(
t
,
v
,
tb
)
if
v
is
None
:
errstream
.
write
(
'%s
\
n
'
%
t
.
__name__
)
else
:
traceback
.
print_exception
(
t
ype
,
value
,
tb
,
file
=
errstream
)
traceback
.
print_exception
(
t
,
v
,
tb
,
file
=
errstream
)
del
tb
try
:
...
...
@@ -576,7 +585,7 @@ class Hub(WaitOperationsGreenlet):
except
:
# pylint:disable=bare-except
traceback
.
print_exc
(
file
=
self
.
exception_stream
)
context
=
repr
(
context
)
errstream
.
write
(
'%s failed with %s
\
n
\
n
'
%
(
context
,
getattr
(
t
ype
,
'__name__'
,
'exception'
),
))
errstream
.
write
(
'%s failed with %s
\
n
\
n
'
%
(
context
,
getattr
(
t
,
'__name__'
,
'exception'
),
))
def
run
(
self
):
...
...
src/gevent/testing/__init__.py
View file @
9b4085b5
...
...
@@ -25,6 +25,13 @@ import unittest
# pylint:disable=unused-import
# It's important to do this ASAP, because if we're monkey patched,
# then importing things like the standard library test.support can
# need to construct the hub (to check for IPv6 support using a socket).
from
.hub
import
QuietHub
import
gevent.hub
gevent
.
hub
.
set_default_hub_class
(
QuietHub
)
from
.sysinfo
import
VERBOSE
from
.sysinfo
import
WIN
from
.sysinfo
import
LINUX
...
...
@@ -87,9 +94,7 @@ from .exception import ExpectedException
from
.leakcheck
import
ignores_leakcheck
from
.params
import
LARGE_TIMEOUT
from
.params
import
DEFAULT_LOCAL_HOST_ADDR
from
.params
import
DEFAULT_LOCAL_HOST_ADDR6
from
.params
import
DEFAULT_BIND_ADDR
...
...
@@ -103,10 +108,6 @@ from .params import DEFAULT_XPC_SOCKET_TIMEOUT
main
=
unittest
.
main
SkipTest
=
unittest
.
SkipTest
from
.hub
import
QuietHub
import
gevent.hub
gevent
.
hub
.
set_default_hub_class
(
QuietHub
)
...
...
src/gevent/testing/hub.py
View file @
9b4085b5
...
...
@@ -29,10 +29,16 @@ class QuietHub(Hub):
EXPECTED_TEST_ERROR
=
(
ExpectedException
,)
def
handle_error
(
self
,
context
,
type
,
value
,
tb
):
if
(
type
,
value
,
tb
)
==
(
None
,
None
,
None
):
import
sys
type
,
value
,
tb
=
sys
.
exc_info
()
type
,
value
,
tb
=
self
.
_normalize_exception
(
type
,
value
,
tb
)
if
issubclass
(
type
,
self
.
EXPECTED_TEST_ERROR
):
# Don't print these to cut down on the noise in the test logs
return
return
Hub
.
handle_error
(
self
,
context
,
type
,
value
,
tb
)
def
print_exception
(
self
,
context
,
t
,
v
,
tb
):
t
,
v
,
tb
=
self
.
_normalize_exception
(
t
,
v
,
tb
)
if
issubclass
(
t
,
self
.
EXPECTED_TEST_ERROR
):
# see handle_error
return
return
Hub
.
print_exception
(
self
,
context
,
t
,
v
,
tb
)
src/gevent/tests/monkey_package/threadpool_monkey_patches.py
View file @
9b4085b5
...
...
@@ -26,3 +26,5 @@ def thread_is_greenlet():
is_greenlet
=
get_hub
().
threadpool
.
apply
(
thread_is_greenlet
)
print
(
is_greenlet
)
print
(
len
(
sys
.
_current_frames
()))
sys
.
stdout
.
flush
()
sys
.
stderr
.
flush
()
src/gevent/tests/test__close_backend_fd.py
View file @
9b4085b5
...
...
@@ -4,7 +4,7 @@ import unittest
import
gevent
from
gevent
import
core
from
gevent.hub
import
Hub
@
unittest
.
skipUnless
(
getattr
(
core
,
'LIBEV_EMBED'
,
False
),
...
...
@@ -23,7 +23,7 @@ class Test(unittest.TestCase):
getattr
(
unittest
.
TestCase
,
'assertRaisesRegexp'
))
def
_check_backend
(
self
,
backend
):
hub
=
gevent
.
get_h
ub
(
backend
,
default
=
False
)
hub
=
H
ub
(
backend
,
default
=
False
)
try
:
self
.
assertEqual
(
hub
.
loop
.
backend
,
backend
)
...
...
@@ -33,8 +33,9 @@ class Test(unittest.TestCase):
raise
unittest
.
SkipTest
(
"backend %s lacks fileno"
%
(
backend
,))
os
.
close
(
fileno
)
with
self
.
assertRaisesRegex
(
SystemError
,
"(libev)"
):
gevent
.
sleep
(
0.001
)
if
backend
!=
'kqueue'
:
with
self
.
assertRaisesRegex
(
SystemError
,
"(libev)"
):
gevent
.
sleep
(
0.001
)
hub
.
destroy
()
self
.
assertIn
(
'destroyed'
,
repr
(
hub
))
...
...
src/gevent/tests/test__monkey_module_run.py
View file @
9b4085b5
...
...
@@ -37,7 +37,7 @@ class TestRun(unittest.TestCase):
args
+=
[
script
,
'patched'
]
p
=
Popen
(
args
,
stdout
=
PIPE
,
stderr
=
PIPE
,
env
=
env
)
monkey_out
,
monkey_err
=
p
.
communicate
()
self
.
assertEqual
(
0
,
p
.
returncode
,
(
monkey_out
,
monkey_err
))
self
.
assertEqual
(
0
,
p
.
returncode
,
(
p
.
returncode
,
monkey_out
,
monkey_err
))
if
module
:
args
=
[
sys
.
executable
,
"-m"
,
script
,
'stdlib'
]
...
...
src/gevent/tests/test__threadpool.py
View file @
9b4085b5
...
...
@@ -525,146 +525,162 @@ class TestRefCount(TestCase):
gevent
.
sleep
(
0
)
pool
.
kill
()
if
hasattr
(
gevent
.
threadpool
,
'ThreadPoolExecutor'
):
from
concurrent.futures
import
TimeoutError
as
FutureTimeoutError
from
concurrent.futures
import
wait
as
cf_wait
from
concurrent.futures
import
as_completed
as
cf_as_completed
from
gevent
import
monkey
from
gevent
import
monkey
class
TestTPE
(
_AbstractPoolTest
):
size
=
1
@
greentest
.
skipUnless
(
hasattr
(
gevent
.
threadpool
,
'ThreadPoolExecutor'
),
"Requires ThreadPoolExecutor"
)
class
TestTPE
(
_AbstractPoolTest
):
size
=
1
MAP_IS_GEN
=
True
MAP_IS_GEN
=
True
ClassUnderTest
=
gevent
.
threadpool
.
ThreadPoolExecutor
@
property
def
ClassUnderTest
(
self
):
return
gevent
.
threadpool
.
ThreadPoolExecutor
MONKEY_PATCHED
=
False
MONKEY_PATCHED
=
False
@
greentest
.
ignores_leakcheck
def
test_future
(
self
):
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
@
property
def
FutureTimeoutError
(
self
):
from
concurrent.futures
import
TimeoutError
as
FutureTimeoutError
return
FutureTimeoutError
calledback
=
[]
@
property
def
cf_wait
(
self
):
from
concurrent.futures
import
wait
as
cf_wait
return
cf_wait
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
@
property
def
cf_as_completed
(
self
):
from
concurrent.futures
import
as_completed
as
cf_as_completed
return
cf_as_completed
def
callback
(
future
):
future
.
calledback
+=
1
raise
greentest
.
ExpectedException
(
"Expected, ignored"
)
@
greentest
.
ignores_leakcheck
def
test_future
(
self
):
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
future
.
calledback
=
0
future
.
add_done_callback
(
callback
)
self
.
assertRaises
(
FutureTimeoutError
,
future
.
result
,
timeout
=
0.001
)
calledback
=
[]
def
spawned
():
return
2016
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
spawned_greenlet
=
gevent
.
spawn
(
spawned
)
def
callback
(
future
):
future
.
calledback
+=
1
raise
greentest
.
ExpectedException
(
"Expected, ignored"
)
# Whether or not we are monkey patched, the background
# greenlet we spawned got to run while we waited.
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
future
.
calledback
=
0
future
.
add_done_callback
(
callback
)
self
.
assertRaises
(
self
.
FutureTimeoutError
,
future
.
result
,
timeout
=
0.001
)
self
.
assertEqual
(
future
.
result
(),
42
)
self
.
assertTrue
(
future
.
done
())
self
.
assertFalse
(
future
.
cancelled
())
# Make sure the notifier has a chance to run so the call back
# gets called
gevent
.
sleep
()
self
.
assertEqual
(
future
.
calledback
,
1
)
def
spawned
():
return
2016
self
.
assertTrue
(
spawned_greenlet
.
ready
())
self
.
assertEqual
(
spawned_greenlet
.
value
,
2016
)
spawned_greenlet
=
gevent
.
spawn
(
spawned
)
# Adding the callback again runs immediately
future
.
add_done_callback
(
lambda
f
:
calledback
.
append
(
True
))
self
.
assertEqual
(
calledback
,
[
True
])
# Whether or not we are monkey patched, the background
# greenlet we spawned got to run while we waited.
# We can wait on the finished future
done
,
_not_done
=
cf_wait
((
future
,))
self
.
assertEqual
(
list
(
done
),
[
future
])
self
.
assertEqual
(
future
.
result
(),
42
)
self
.
assertTrue
(
future
.
done
())
self
.
assertFalse
(
future
.
cancelled
())
# Make sure the notifier has a chance to run so the call back
# gets called
gevent
.
sleep
()
self
.
assertEqual
(
future
.
calledback
,
1
)
self
.
assertEqual
(
list
(
cf_as_completed
((
future
,))),
[
future
])
# Doing so does not call the callback again
self
.
assertEqual
(
future
.
calledback
,
1
)
# even after a trip around the event loop
gevent
.
sleep
()
self
.
assertEqual
(
future
.
calledback
,
1
)
pool
.
kill
()
del
future
del
pool
del
self
.
pool
@
greentest
.
ignores_leakcheck
def
test_future_wait_module_function
(
self
):
# Instead of waiting on the result, we can wait
# on the future using the module functions
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
if
self
.
MONKEY_PATCHED
:
# Things work as expected when monkey-patched
_done
,
not_done
=
cf_wait
((
future
,),
timeout
=
0.001
)
self
.
assertEqual
(
list
(
not_done
),
[
future
])
def
spawned
():
return
2016
spawned_greenlet
=
gevent
.
spawn
(
spawned
)
done
,
_not_done
=
cf_wait
((
future
,))
self
.
assertEqual
(
list
(
done
),
[
future
])
self
.
assertTrue
(
spawned_greenlet
.
ready
())
self
.
assertEqual
(
spawned_greenlet
.
value
,
2016
)
else
:
# When not monkey-patched, raises an AttributeError
self
.
assertRaises
(
AttributeError
,
cf_wait
,
(
future
,))
pool
.
kill
()
del
future
del
pool
del
self
.
pool
@
greentest
.
ignores_leakcheck
def
test_future_wait_gevent_function
(
self
):
# The future object can be waited on with gevent functions.
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
self
.
assertTrue
(
spawned_greenlet
.
ready
())
self
.
assertEqual
(
spawned_greenlet
.
value
,
2016
)
# Adding the callback again runs immediately
future
.
add_done_callback
(
lambda
f
:
calledback
.
append
(
True
))
self
.
assertEqual
(
calledback
,
[
True
])
# We can wait on the finished future
done
,
_not_done
=
self
.
cf_wait
((
future
,))
self
.
assertEqual
(
list
(
done
),
[
future
])
self
.
assertEqual
(
list
(
self
.
cf_as_completed
((
future
,))),
[
future
])
# Doing so does not call the callback again
self
.
assertEqual
(
future
.
calledback
,
1
)
# even after a trip around the event loop
gevent
.
sleep
()
self
.
assertEqual
(
future
.
calledback
,
1
)
pool
.
kill
()
del
future
del
pool
del
self
.
pool
@
greentest
.
ignores_leakcheck
def
test_future_wait_module_function
(
self
):
# Instead of waiting on the result, we can wait
# on the future using the module functions
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
if
self
.
MONKEY_PATCHED
:
# Things work as expected when monkey-patched
_done
,
not_done
=
self
.
cf_wait
((
future
,),
timeout
=
0.001
)
self
.
assertEqual
(
list
(
not_done
),
[
future
])
def
spawned
():
return
2016
spawned_greenlet
=
gevent
.
spawn
(
spawned
)
done
=
gevent
.
wait
((
future
,))
done
,
_not_done
=
self
.
cf_
wait
((
future
,))
self
.
assertEqual
(
list
(
done
),
[
future
])
self
.
assertTrue
(
spawned_greenlet
.
ready
())
self
.
assertEqual
(
spawned_greenlet
.
value
,
2016
)
else
:
# When not monkey-patched, raises an AttributeError
self
.
assertRaises
(
AttributeError
,
self
.
cf_wait
,
(
future
,))
pool
.
kill
()
del
future
del
pool
del
self
.
pool
@
greentest
.
ignores_leakcheck
def
test_future_wait_gevent_function
(
self
):
# The future object can be waited on with gevent functions.
self
.
assertEqual
(
monkey
.
is_module_patched
(
'threading'
),
self
.
MONKEY_PATCHED
)
pool
=
self
.
pool
pool
.
kill
()
del
future
del
pool
del
self
.
pool
def
fn
():
gevent
.
sleep
(
0.5
)
return
42
future
=
pool
.
submit
(
fn
)
# pylint:disable=no-member
def
spawned
():
return
2016
spawned_greenlet
=
gevent
.
spawn
(
spawned
)
done
=
gevent
.
wait
((
future
,))
self
.
assertEqual
(
list
(
done
),
[
future
])
self
.
assertTrue
(
spawned_greenlet
.
ready
())
self
.
assertEqual
(
spawned_greenlet
.
value
,
2016
)
pool
.
kill
()
del
future
del
pool
del
self
.
pool
if
__name__
==
'__main__'
:
...
...
src/gevent/tests/test__threadpool_executor_patched.py
View file @
9b4085b5
from
__future__
import
print_function
from
__future__
import
absolute_import
from
__future__
import
division
from
gevent
import
monkey
;
monkey
.
patch_all
()
import
gevent.testing
as
greentest
import
gevent.threadpool
if
hasattr
(
gevent
.
threadpool
,
'ThreadPoolExecutor'
):
from
.
import
test__threadpool
from
test__threadpool
import
TestTPE
as
_Base
class
TestPatchedTPE
(
_Base
):
MONKEY_PATCHED
=
True
class
TestPatchedTPE
(
test__threadpool
.
TestTPE
):
MONKEY_PATCHED
=
True
del
_Base
if
__name__
==
'__main__'
:
greentest
.
main
()
src/gevent/threadpool.py
View file @
9b4085b5
...
...
@@ -579,22 +579,21 @@ else:
from
gevent._util
import
Lazy
from
concurrent.futures
import
_base
as
cfb
def
_
wrap_error
(
future
,
fn
):
def
_
ignore_error
(
future_proxy
,
fn
):
def
cbwrap
(
_
):
del
_
# we're called with the async result, but
# be sure to pass in ourself. Also automatically
# unlink ourself so that we don't get called multiple
# times.
# We're called with the async result (from the threadpool), but
# be sure to pass in the user-visible _FutureProxy object..
try
:
fn
(
future
)
fn
(
future
_proxy
)
except
Exception
:
# pylint: disable=broad-except
future
.
hub
.
handle_error
((
fn
,
future
),
None
,
None
,
None
)
# Just print, don't raise to the hub's parent.
future_proxy
.
hub
.
print_exception
((
fn
,
future_proxy
),
None
,
None
,
None
)
return
cbwrap
def
_wrap
(
future
,
fn
):
def
_wrap
(
future
_proxy
,
fn
):
def
f
(
_
):
fn
(
future
)
fn
(
future
_proxy
)
return
f
class
_FutureProxy
(
object
):
...
...
@@ -656,10 +655,11 @@ else:
raise
concurrent
.
futures
.
TimeoutError
()
def
add_done_callback
(
self
,
fn
):
"""Exceptions raised by *fn* are ignored."""
if
self
.
done
():
fn
(
self
)
else
:
self
.
asyncresult
.
rawlink
(
_
wrap
_error
(
self
,
fn
))
self
.
asyncresult
.
rawlink
(
_
ignore
_error
(
self
,
fn
))
def
rawlink
(
self
,
fn
):
self
.
asyncresult
.
rawlink
(
_wrap
(
self
,
fn
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment