From 4e19ef0ae0a235f1f4d52c32bbd79b2309b159f9 Mon Sep 17 00:00:00 2001 From: ergoithz Date: Fri, 24 Jul 2020 21:03:32 +0100 Subject: [PATCH 1/7] add intermediate storage and performance examples --- README.md | 2 + docs/index.rst | 2 + examples/performance.md | 100 +++++++++++++++++++++++++++++++++ examples/result_storage.md | 72 ++++++++++++++++++++++++ testbench/forwarded_proxies.py | 22 -------- testbench/uactor.py | 7 --- 6 files changed, 176 insertions(+), 29 deletions(-) create mode 100644 examples/performance.md create mode 100644 examples/result_storage.md delete mode 100644 testbench/forwarded_proxies.py delete mode 100644 testbench/uactor.py diff --git a/README.md b/README.md index 3f5e763..4e9d003 100644 --- a/README.md +++ b/README.md @@ -189,10 +189,12 @@ take a look at our code examples. * [Actor lifetime](./examples/lifetime.md). * [Result proxies](./examples/result_proxies.md). * [Method callbacks](./examples/callbacks.md). + * [Performance tips](./examples/performance.md). * Advanced patterns: * [Sticky processes](./examples/stick.md). * [Actor pool](./examples/pool.md). + * [Intermediate result storage](./examples/result_storage.md). * [Networking](./examples/networking.md). ## uActor design diff --git a/docs/index.rst b/docs/index.rst index c759261..4654bf9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,6 +34,7 @@ with no dependencies other than the `Python Standard Library`_. examples/lifetime examples/result_proxies examples/callbacks + examples/performance .. toctree:: :maxdepth: 2 @@ -41,6 +42,7 @@ with no dependencies other than the `Python Standard Library`_. examples/stick examples/pool + examples/result_storage examples/networking Indices and tables diff --git a/examples/performance.md b/examples/performance.md new file mode 100644 index 0000000..098df4f --- /dev/null +++ b/examples/performance.md @@ -0,0 +1,100 @@ +# Performance tips + +Python is not the faster platform out there, and +[inter-process communication][ipc] suffer of both serialization +and data transfer overhead, but there is always somethign we can do +to alleviate this. + +## Simplify serialized data + +Using simpler data types (like python primitives) will dramatically reduce +the time spent on serialization, while reducing the chance of transferring +unnecessary data. + +## Custom serialization + +When defining your own classes aimed to be sent to and from actors, you +should consider implementing some [pickle] serialization +[interfaces][pickle-reduce] in order to customize how they will be serialized, +this way unnecessary data can be striped out. + +## Using external storage for big data-streams + +Sometimes, actors need to transfer huge loads of data between them. + +Message-passing protocols are usually not the best at this, but you +can storing that data somewhere else while only sending, as the message, +what's necessary to externally retrieve that data from there. + +You can see how to achieve this in our +[Intermediate result storage](./result_storage.md) section. + +## Pickle5 (hack) + +Traditionally, [multiprocessing], and more specifically [pickle], +were not specially optimized to transfer binary data buffers. + +Python 3.8 introduced the new pickle protocol in [PEP 574][pep574], +greatly optimizing the serialization of [buffer] objects +(like [bytearray], [memoryview], [numpy.ndarray]) by implementing a +dedicated code path. + +For compatibility reasons, [multiprocessing] does not use the latest +pickle protocol available, and does not expose any option to change that +other than patching it globally. + +Here is a patch for Python 3.8 to use the latest pickle protocol available. +```python +import multiprocessing.connection as mpc + +class ForkingPickler5(mpc._ForkingPickler): + @classmethod + def dumps(cls, obj, protocol=-1): + return super().dumps(obj, protocol) + +mpc._ForkingPickler = ForkingPickler5 +``` + +For previous python versions, a [pickle5 backport][pickle5] is available, but +the patch turns out a bit messier because of how tightly coupled the +[multiprocessing] implementation is. + +```python +import io +import multiprocessing.connection as mpc +import pickle5 + +class ForkingPickler5(pickle5.Pickler): + upstream = mpc._ForkingPickler + loads = staticmethod(pickle5.loads) + + @classmethod + def dumps(cls, obj, protocol=-1): + buf = io.BytesIO() + cls(buf, protocol).dump(obj) + return buf.getbuffer() + + def __init__(self, file, protocol=-1, **kwargs): + self.dispatch_table = ( + self + .upstream(file, protocol, **kwargs) + .dispatch_table + ) + super().__init__(file, protocol, **kwargs) + +mpc._ForkingPickler = ForkingPickler5 +``` + +Keep in mind this is no more than a dirty hack, is opt to the implementor +to apply these patches at his own discretion. + +[ipc]: https://en.wikipedia.org/wiki/Inter-process_communication +[pep574]: https://www.python.org/dev/peps/pep-0574/ +[buffer]: https://docs.python.org/3/c-api/buffer.html +[bytearray]: https://docs.python.org/3/library/functions.html#func-bytearray +[pickle]: https://docs.python.org/3/library/pickle.html +[pickle-reduce]: https://docs.python.org/3/library/pickle.html#pickling-class-instances +[memoryview]: https://docs.python.org/3/library/stdtypes.html#memoryview +[numpy.ndarray]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html +[multiprocessing]: https://docs.python.org/3/library/multiprocessing.html +[pickle5]: https://pypi.org/project/pickle5/ diff --git a/examples/result_storage.md b/examples/result_storage.md new file mode 100644 index 0000000..c21fd11 --- /dev/null +++ b/examples/result_storage.md @@ -0,0 +1,72 @@ +# Intermediate result storage + +Sometimes, specially with big payloads, alternative transfer methods can +be considered, since transferring huge data streams using messages +can be quite expensive. + +There are multiple approaches to solve this issue, mostly revolving around +transfering data via external means, some of them will be explained here. + +## Shared memory + +Starting from Python 3.8, [shared memory][shared-memory] enables +multiple processes (running in the same system) to read and write +to the same memory [buffer]. + +Shared memory is exposed as a buffer we can read and write to, +and as such we can use it as a transport for our data. + +This feature plays quite nicely with uActor actors. + +```python +import os +import multiprocessing.managers +import multiprocessing.shared_memory + +import uactor + +class SharedActor(uactor.Actor): + def __init__(self): + self.shared = multiprocessing.managers.SharedMemoryManager() + self.shared.start() + + def shutdown(self): + self.shared.shutdown() + + def get_shared_res(self): + data = f'Shared memory from {os.getpid()}.'.encode() + shared = self.shared.SharedMemory(len(data)) + shared.buf[:] = data + return shared + +with SharedActor() as actor: + shared = actor.get_shared_res() + print(f'Running on {os.getpid()}') + # Running on 11209 + print(shared.buf.tobytes().decode()) + # Shared memory from 47989. + shared.unlink() +``` + +Any kind of structured data, not just bytes, can be transferred this way +using any [serialization] library such as [pickle]. + +## External data stores + +Using a centralized broker where all processes can concurrently store and +retrieve data can be considered, specially when distributing actors over the +network. + +Some in-memory databases such as [memcached] or [redis] as specially good at +it, but you can even use traditional databases or [blob] storage servers. + +Integrating with external services is a whole big subject on its own and +falls outside this document scope. + +[shared-memory]: https://docs.python.org/3/library/multiprocessing.shared_memory.html +[pickle]: https://docs.python.org/3/library/pickle.html +[serialization]: https://en.wikipedia.org/wiki/Serialization +[memcached]: https://www.memcached.org/ +[redis]: https://redis.io/ +[blob]: https://en.wikipedia.org/wiki/Binary_large_object +[buffer]: https://docs.python.org/3/c-api/buffer.html diff --git a/testbench/forwarded_proxies.py b/testbench/forwarded_proxies.py deleted file mode 100644 index d8a57fd..0000000 --- a/testbench/forwarded_proxies.py +++ /dev/null @@ -1,22 +0,0 @@ -import uactor - -class MyActor(uactor.Actor): - _exposed_ = ('my_other_actor',) - - def __init__(self): - self.my_other_actor = MyOtherActor() - -class MyOtherActor(uactor.Actor): - _options_ = {'address': ('0.0.0.0', 7000), 'authkey': b'OtherSecret'} - -with MyActor() as actor: - try: - my_other_actor = actor.my_other_actor - raise RuntimeError('AuthkeyError not raised') - except uactor.AuthkeyError: - pass - address = 'localhost', 7000 - capture = [(('0.0.0.0'), 7000)] - with MyOtherActor.connect(address, b'OtherSecret', capture=capture): - my_other_actor = actor.my_other_actor - print(my_other_actor.connection_address) diff --git a/testbench/uactor.py b/testbench/uactor.py deleted file mode 100644 index 679ee0a..0000000 --- a/testbench/uactor.py +++ /dev/null @@ -1,7 +0,0 @@ - -import pathlib -import sys -sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) -sys.modules.pop('uactor') -from uactor import * -sys.path.pop(0) -- GitLab From 838d675b6217a9f074f1cdec2b1e29f6f2aa8a73 Mon Sep 17 00:00:00 2001 From: "Felipe A. Hernandez" Date: Fri, 9 Oct 2020 16:00:59 +0000 Subject: [PATCH 2/7] update network example, avoid embedded readme image 404 --- .python-version | 2 +- docs/images/uactor-title.96.png | Bin 0 -> 67 bytes examples/networking.md | 28 +++++++++++++++------------- images/uactor-title.128.png | Bin 25848 -> 0 bytes images/uactor.128.png | Bin 11416 -> 0 bytes images/uactor.256.png | Bin 28615 -> 0 bytes tests.py | 9 ++++----- 7 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 docs/images/uactor-title.96.png delete mode 100644 images/uactor-title.128.png delete mode 100644 images/uactor.128.png delete mode 100644 images/uactor.256.png diff --git a/.python-version b/.python-version index 269aa9c..2e14a95 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.8.3 +3.8.6 diff --git a/docs/images/uactor-title.96.png b/docs/images/uactor-title.96.png new file mode 100644 index 0000000000000000000000000000000000000000..91a99b94e23a00cc8133f22e3fe2a0b48a015808 GIT binary patch literal 67 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 diff --git a/images/uactor.256.png b/images/uactor.256.png deleted file mode 100644 index 4be7d76af70d5afb5676eaa78668bcdce27fbff9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28615 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 TRowmz3=9mOu6{1-oD!M Date: Sat, 24 Oct 2020 13:11:56 +0000 Subject: [PATCH 3/7] improve intermediate storage example --- examples/result_storage.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/result_storage.md b/examples/result_storage.md index c21fd11..6430388 100644 --- a/examples/result_storage.md +++ b/examples/result_storage.md @@ -57,14 +57,24 @@ Using a centralized broker where all processes can concurrently store and retrieve data can be considered, specially when distributing actors over the network. -Some in-memory databases such as [memcached] or [redis] as specially good at -it, but you can even use traditional databases or [blob] storage servers. +Some in-memory databases such as [memcached] or [redis] are specially good at +storing temporary data, but even traditional databases or [blob] storage +services could be used. + +When talking about local-only tasks, [tempfile.NamedTemporaryFile] could +be also a good option when used alongside RAM-based virtual filesystems like +[tmpfs]. Regular filesystems should be only considered for objects +being forwarded across many actors since even the fastest block device +is still times slower than regular [multiprocessing] mechanisms. Integrating with external services is a whole big subject on its own and falls outside this document scope. [shared-memory]: https://docs.python.org/3/library/multiprocessing.shared_memory.html [pickle]: https://docs.python.org/3/library/pickle.html +[tempfile.NamedTemporaryFile]: https://docs.python.org/3/library/pickle.html +[multiprocessing]: https://docs.python.org/3/library/multiprocessing.html +[tmpfs]: https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html#tmpfs "Linux tmpfs" [serialization]: https://en.wikipedia.org/wiki/Serialization [memcached]: https://www.memcached.org/ [redis]: https://redis.io/ -- GitLab From e92c3b178a59dbb551fb4ff7e7e3c748762d4d0d Mon Sep 17 00:00:00 2001 From: "Felipe A. Hernandez" Date: Thu, 10 Dec 2020 10:33:38 +0000 Subject: [PATCH 4/7] Cleanup, year bump. --- LICENSE | 2 +- README.md | 25 +++++++++++++++---------- setup.py | 51 ++++++++++++++++++++++----------------------------- uactor.py | 2 +- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/LICENSE b/LICENSE index 7b10dea..c95a2ff 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Felipe A Hernandez +Copyright (c) 2020-2021 Felipe A Hernandez Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4e9d003..0748fce 100644 --- a/README.md +++ b/README.md @@ -207,12 +207,12 @@ are still pretty rare (except for distributed processing frameworks like single core. * [multiprocessing][multiprocessing], meant to overcome threading limitations by using processes, exposes a pretty convoluted API as processes - are way more complex, exposing many quirks and limitations. + are way more complex, with many quirks and platform limitations. uActor allows implementing distributed software as easy as just declaring and instancing classes, following the [actor model][actor-model], by thinly -wrapping the standard [SyncManager][syncmanager] to circumvent most o - [multiprocessing][multiprocessing] complexity and some of its flaws. +wrapping the standard [SyncManager][syncmanager] to circumvent most of +[multiprocessing][multiprocessing] complexity and some of its flaws. uActor API is designed to be both minimalistic and intuitive, but still few compromises had to be taken to leverage on [SyncManager][syncmanager] @@ -269,6 +269,7 @@ class Actor(uactor.Actor): actor = Actor() print('My process id is', os.getpid()) # My process id is 153333 + print('Actor process id is ', actor.getpid()) # Actor process id is 153344 ``` @@ -293,7 +294,10 @@ import uactor class Actor(uactor.Actor): _method_to_typeid_ = {'get_mapping': 'dict'} - ... + + def __init__(self): + self.my_data_dict = {} + def get_data(self): return self.my_data_dict ``` @@ -305,7 +309,9 @@ and a proxy `typeid` (as in [SyncManager][syncmanager] semantics). import uactor class Actor(uactor.Actor): - ... + def __init__(self): + self.my_data_dict = {} + def get_data(self): return uactor.proxy(self.my_data_dict, 'dict') ``` @@ -325,7 +331,6 @@ class MyDataProxy(uactor.BaseProxy): def my_method(self): return self._callmethod('my_method') - my_other_method = uactor.ProxyMethod('my_other_method') class Actor(uactor.Actor): _proxies_ = {'MyDataProxy': MyDataProxy} @@ -357,15 +362,15 @@ print(uactor.ActorManager.typeids()) uActor is deliberately very small in scope, while still aiming to be easily extended, so extra functionality might be implemented via external means. -If you find any bug or a possible improvement to existing functionality it +If you find any bug or a possible improvement to existing functionality, it will likely be accepted so feel free to contribute. -If, in the other hand, you feel a feature is missing, you can either create -another library using uActor as dependency or fork this project. +If, in the other hand, you think a feature could be missing, you can either +create another library using uActor as dependency or fork this project. ## License -Copyright (c) 2020, Felipe A Hernandez. +Copyright (c) 2020-2021, Felipe A Hernandez. MIT License (see [LICENSE](./LICENSE)). diff --git a/setup.py b/setup.py index cdedf35..404451a 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ at the `project repository `_. License ------- -Copyright (c) 2020, Felipe A Hernandez. +Copyright (c) 2020-2021, Felipe A Hernandez. MIT License (see `LICENSE`_). @@ -18,43 +18,36 @@ MIT License (see `LICENSE`_). """ import datetime -import io +import pathlib import os import re from setuptools import setup repository = 'https://gitlab.com/ergoithz/uactor' - -with io.open('README.md', 'rt', encoding='utf8') as f: - content = f.read() - readme = re.sub( - r'(?P!?)\[(?P[^]]+)\]\(\./(?P[^)]+)\)', - lambda match: ( - '{prefix}[{text}]({repository}/-/{view}/master/{src})'.format( - repository=repository, - view='raw' if match.group('prefix') == '!' else 'blob', - **match.groupdict(), - )), - content, - ) - -with io.open('uactor.py', 'rt', encoding='utf8') as f: - content = f.read() - __author__, __email__, __license__, __version__ = filter(None, ( - re.search(rf"__{name}__ = '([^']+)'", content).group(1) - for name in ('author', 'email', 'license', 'version') - )) - version = ( - __version__ - if os.getenv('TWINE_REPOSITORY') == 'pypi' else - '{}a{}'.format(__version__, datetime.date.today().strftime('%Y%m%d')) - ) - +readme = re.sub( + r'(?P
!?)\[(?P[^]]+)\]\(\./(?P[^)]+)\)',
+    lambda match: '{pre}[{txt}]({repo}/-/{view}/master/{src})'.format(
+        repo=repository,
+        view='raw' if match.group('pre') else 'blob',
+        **match.groupdict(),
+        ),
+    pathlib.Path('README.md').read_text(),
+    )
+code = pathlib.Path('uactor.py').read_text()
+__author__, __email__, __license__, __version__ = (
+    re.search(rf"__{name}__ = '([^']+)'", content).group(1)
+    for name in ('author', 'email', 'license', 'version')
+    ))
+version = (
+    __version__
+    if os.getenv('TWINE_REPOSITORY') == 'pypi' else
+    '{}a{}'.format(__version__, datetime.date.today().strftime('%Y%m%d'))
+    )
 setup(
     name='uactor',
     version=version,
-    url=repository,
+    url='https://uactor.readthedocs.io/en/latest/',
     license=__license__,
     author=__author__,
     author_email=__email__,
diff --git a/uactor.py b/uactor.py
index bb0b271..6da9e3f 100644
--- a/uactor.py
+++ b/uactor.py
@@ -12,7 +12,7 @@ at the `project repository`_.
 License
 -------
 
-Copyright (c) 2020, Felipe A Hernandez.
+Copyright (c) 2020-2021, Felipe A Hernandez.
 
 MIT License (see `LICENSE`_).
 
-- 
GitLab


From 50a3f573a07360c7a1319b2e3934b459a7b2ae42 Mon Sep 17 00:00:00 2001
From: "Felipe A. Hernandez" 
Date: Thu, 10 Dec 2020 11:10:35 +0000
Subject: [PATCH 5/7] Fix setup py, reorder pipeline.

---
 .gitlab-ci.yml | 18 +++++++++---------
 setup.py       | 33 +++++++++++++++------------------
 2 files changed, 24 insertions(+), 27 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dbb3e4f..ac14c3b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -52,23 +52,23 @@ python39:
   - apk add build-base
   - pip install tox
 
-coverage:
+docs:
   stage: report
   extends: .python
   artifacts:
-    when: always
+    when: on_success
     paths:
-    - htmlcov
-  coverage: '/^(?:uactor\.py|TOTAL)\s+(?:\d+\s+)*(\d+\%)$/'
+    - docs/build
   script:
-  - tox -e coverage
+  - tox -e docs
 
-docs:
+coverage:
   stage: report
   extends: .python
   artifacts:
-    when: on_success
+    when: always
     paths:
-    - docs/build
+    - htmlcov
+  coverage: '/^(?:uactor\.py|TOTAL)\s+(?:\d+\s+)*(\d+\%)$/'
   script:
-  - tox -e docs
+  - tox -e coverage
diff --git a/setup.py b/setup.py
index 404451a..22d1253 100644
--- a/setup.py
+++ b/setup.py
@@ -24,35 +24,32 @@ import re
 
 from setuptools import setup
 
-repository = 'https://gitlab.com/ergoithz/uactor'
-readme = re.sub(
-    r'(?P
!?)\[(?P[^]]+)\]\(\./(?P[^)]+)\)',
-    lambda match: '{pre}[{txt}]({repo}/-/{view}/master/{src})'.format(
-        repo=repository,
-        view='raw' if match.group('pre') else 'blob',
-        **match.groupdict(),
-        ),
-    pathlib.Path('README.md').read_text(),
-    )
 code = pathlib.Path('uactor.py').read_text()
 __author__, __email__, __license__, __version__ = (
-    re.search(rf"__{name}__ = '([^']+)'", content).group(1)
+    re.search(rf"__{name}__ = '([^']+)'", code).group(1)
     for name in ('author', 'email', 'license', 'version')
-    ))
-version = (
-    __version__
-    if os.getenv('TWINE_REPOSITORY') == 'pypi' else
-    '{}a{}'.format(__version__, datetime.date.today().strftime('%Y%m%d'))
     )
 setup(
     name='uactor',
-    version=version,
+    version=(
+        __version__
+        if os.getenv('TWINE_REPOSITORY') == 'pypi' else
+        '{}a{}'.format(__version__, datetime.date.today().strftime('%Y%m%d'))
+        ),
     url='https://uactor.readthedocs.io/en/latest/',
     license=__license__,
     author=__author__,
     author_email=__email__,
     description='uActor: Process Actor Model',
-    long_description=readme,
+    long_description=re.sub(
+        r'(?P
!?)\[(?P[^]]+)\]\(\./(?P[^)]+)\)',
+        lambda match: '{pre}[{text}]({repo}/-/{mode}/master/{src})'.format(
+            repo='https://gitlab.com/ergoithz/uactor',
+            mode='raw' if match.group('pre') == '!' else 'blob',
+            **match.groupdict(),
+            ),
+        pathlib.Path('README.md').read_text(),
+        ),
     long_description_content_type='text/markdown',
     classifiers=[
         'License :: OSI Approved :: MIT License',
-- 
GitLab


From a8576306712bf99efa756000a953878bb7a6fb98 Mon Sep 17 00:00:00 2001
From: ergoithz 
Date: Tue, 5 Jan 2021 20:55:50 +0000
Subject: [PATCH 6/7] improve documentation, ci

---
 .gitlab-ci.yml             |  14 ++-
 .python-version            |   2 +-
 examples/callbacks.md      |   1 +
 examples/inheritance.md    |   2 +
 examples/lifetime.md       |   1 +
 examples/networking.md     | 205 ++++++++++++++++++++++++-------------
 examples/performance.md    |  59 ++++++-----
 examples/pool.md           |  33 ++++--
 examples/result_proxies.md |  76 +++++++++++++-
 examples/result_storage.md |   7 +-
 examples/stick.md          |   3 +-
 setup.py                   |   2 +-
 12 files changed, 285 insertions(+), 120 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ac14c3b..aca69f2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -33,21 +33,25 @@ pypy3:
   extends: .test
   image: pypy:3-slim
 
-python36:
+python306:
   extends: .test
   image: python:3.6-alpine
 
-python37:
+python307:
   extends: .test
   image: python:3.7-alpine
 
-python38:
+python308:
   extends: .test
   image: python:3.8-alpine
 
-python39:
+python309:
   extends: .test
-  image: python:3.9-rc-alpine
+  image: python:3.9-alpine
+
+python310:
+  extends: .test
+  image: python:3.10-rc-alpine
   before_script:
   - apk add build-base
   - pip install tox
diff --git a/.python-version b/.python-version
index 2e14a95..6bd1074 100644
--- a/.python-version
+++ b/.python-version
@@ -1 +1 @@
-3.8.6
+3.9.1
diff --git a/examples/callbacks.md b/examples/callbacks.md
index 498986a..c6cdc16 100644
--- a/examples/callbacks.md
+++ b/examples/callbacks.md
@@ -8,6 +8,7 @@ while waiting for results.
 This can be very useful when used along with asynchronous
 [result proxies](./result_proxies.md).
 
+Example:
 ```python
 import uactor
 
diff --git a/examples/inheritance.md b/examples/inheritance.md
index e42033c..79b7643 100644
--- a/examples/inheritance.md
+++ b/examples/inheritance.md
@@ -3,6 +3,7 @@
 Actor inheritance works just as regular python inheritance (just a few
 caveats on special attributes, see below).
 
+Example:
 ```python
 import os
 import uactor
@@ -45,6 +46,7 @@ Actor configuration attributes `_exposed_`, `_proxies_` and
 are honored), so you don't need to carry parent values manually when updating
 them.
 
+Example:
 ```python
 import uactor
 
diff --git a/examples/lifetime.md b/examples/lifetime.md
index 0d320d4..aa1aa6f 100644
--- a/examples/lifetime.md
+++ b/examples/lifetime.md
@@ -6,6 +6,7 @@ needed, freeing them after that, and actors are not an exception to this.
 Actors expose both [context manager protocol][context-managers] and `shutdown`
 methods to enable finalizing the actor process once is no longer required.
 
+Example:
 ```python
 import uactor
 
diff --git a/examples/networking.md b/examples/networking.md
index b4be0c5..0b3c3c1 100644
--- a/examples/networking.md
+++ b/examples/networking.md
@@ -1,26 +1,26 @@
 ### Networking
 
 Actors will default to the most efficient method of inter-process
-communication available.
+communication available, which often relies on local sockets or pipes.
 
-But in some cases, you may want to distribute workloads between different
-machines on a same network over TCP/IP. This can by done by defining
-the appropriate addresses on your actors.
+Alternatively, actors can listen to an TCP/IP address and distributed
+across different machines over networks, rendering **uActor** as a great
+tool for distributed computing.
 
-Keep in mind that the same actors classes must be available, at the same
-importable module names, in all the involved parties.
+Keep in mind that, when following this approach, due object serialization,
+actors classes (and any serialized type) still have to be available
+at the same importable module names, in all the involved parties.
 
 #### Connection
 
-We can declare our network actor as usual, but customizing the options
-forwarded to `ActorManager` with a TCP address (in the example below,
-`0.0.0.0` means listening to all addresses, while `0` means choosing a random
-port), we also need to specify an authtoken (the authentication secret),
-initialize the actor (listening to incoming connections), and print at
-which port is the actor listening at.
+When declaring an actor, we can define we want it to listen to a TCP/IP
+interface by specifying that on his `_options_` attribute, along with an
+explicit `authkey` secret that clients will need to authenticate.
 
-We'll name this module `network_actor.py` to be used later.
+We would need the value of `uactor.Actor.connection_address` to know which
+address an actor is available at.
 
+Example (`network_actor.py`, server and library):
 ```python
 import os
 import time
@@ -28,7 +28,7 @@ import uactor
 
 class NetworkActor(uactor.Actor):
 
-    # Actor manager options to listen over TCP on a random port
+    # Actor manager options to listen over all TCP on a random port
     _options_ = {'address': ('0.0.0.0', 0), 'authkey': b'SECRET'}
 
     def getpid(self):
@@ -44,13 +44,10 @@ if __name__ == '__main__':
             time.sleep(10)
 ```
 
-We can now connect, remotely, to the same actor process with the
-`uactor.Actor.connect` method with the correct `authkey`, keep
-in mind both proxy address hostname and port to reach its actor process
-can vary at different network locations.
-
-This is a remote connection example (importing the actor class from above):
+We can use `uactor.Actor.connect` classmethod in conjunction with the address
+available at `uactor.Actor.connection_address` to connect to a remote actor.
 
+Example (client):
 ```python
 from network_actor import NetworkActor
 
@@ -64,16 +61,13 @@ with NetworkActor.connect(address, b'SECRET') as actor:
 
 #### Forwarded proxies
 
-One neat feature of uActor is proxy forwarding, that is, being able
-to pass proxies as arguments or return them, to and from actors.
-
-But when forwarding proxies from actors with different secrets, complexity
-adds up pretty quickly.
-
-If a proxy returns a foreign proxy from an actor we aren't connected to, an
-`AuthkeyError` will be raised because a connection won't be possible due
-unknown authkey.
+When networking, because of connections are made manually via
+`uactor.Actor.connect` (and as such, actors being considered remote),
+when receiving a foreign proxy to an actor we aren't connected to, an
+`AuthkeyError` could be raised either because unknown `authkey`
+(see [proxy forwarding][proxy-forwarding]) or because of an invalid address.
 
+Example (`network_proxy_forwarding.py`, library):
 ```python
 import uactor
 
@@ -86,16 +80,25 @@ class MyActor(uactor.Actor):
 class MyOtherActor(uactor.Actor):
     _options_ = {'address': ('0.0.0.0', 7000), 'authkey': b'OtherSecret'}
 
+```
+
+Example (raising `AuthkeyError` with a remote actor):
+```python
+from network_proxy_forwarding import MyActor
+
 with MyActor() as actor:
     my_other_actor = actor.my_other_actor
     # AuthKeyError
 ```
 
-We need to connect to actors before being able to take proxies pointing to
-them, so its authkey can be defined, allowing their remote addresses
-translated to local ones, if necessary.
+We need to connect to actors before being able to handle their proxies,
+as its `authkey` must be set beforehand, while enabling remote address
+translation when necessary (via `uactor.Actor.connect` `capture` parameter).
 
+Example:
 ```python
+from network_proxy_forwarding import MyActor
+
 with MyActor() as actor:
     address = 'localhost', 7000
     capture = [('0.0.0.0', 7000)]
@@ -103,33 +106,16 @@ with MyActor() as actor:
         my_other_actor = actor.my_other_actor
 ```
 
-This connection can be performed, alternatively, during exception handling,
-skipping this step on the few cases this is not strictly required.
+For further information head to the [proxy forwarding][proxy-forwarding]
+section.
 
-```python
-with MyActor() as actor:
-    try:
-        my_other_actor = actor.my_other_actor
-    except uactor.AuthKeyError as e:
-        address = 'localhost', 7000
-        capture = [('0.0.0.0', 7000)]
-        with MyOtherActor.connect(address, b'OtherSecret', capture=capture):
-            my_other_actor = actor.my_other_actor
-```
+#### Remote shutdown
 
-Both approaches have their pros and cons, is opt to developers to
-choose wisely between them, based on the side-effects and specially on
-their implementation.
-
-#### Server mainloop with remote shutdown
-
-Since actor processes are only kept alive as long of their parent processes
-are running, we can implement some simple logic to keep it around until needed
-while allowing remote shutdowns.
-
-We'll name this example module `network_actor.py` to be reused later as a
-library by a remote client.
+By design, actor processes are kept alive as long of their parent processes
+are running. We can enable remote clients to shutdown an actor process
+via additional logic on the parent process (mainloop).
 
+Example (`network_actor_shutdown.py`, server and library):
 ```python
 import threading
 import uactor
@@ -146,11 +132,11 @@ class NetworkActor(uactor.Actor):
         self.finished.set()
 
     def wait(self, timeout=-1):
-        return self.finished,wait(timeout)
+        return self.finished.wait(timeout)
 
 if __name__ == '__main__':
     with NetworkActor() as actor:
-        while not actor.wait(timeout=10):  # avoid socket timeouts
+        while not actor.wait(timeout=10):  # timeout avoids socket timeouts
             pass
 ```
 
@@ -158,23 +144,21 @@ This way, a remote proxy will be able to end the mainloop by calling
 shutdown and end the owner process mainloop, effectively finishing
 the process. We'll import the actor class from the previous example.
 
+Example:
 ```python
-from network_actor import NetworkActor
+from network_actor_shutdown import NetworkActor
 
 address = 'localhost', 6000
 external = NetworkActor.connect(address, b'SECRET')
 external.shutdown()
 ```
 
-#### Autodiscovery
-
-To enable dynamic actor discovery, we might keep an central actor listening
-to an specific port, acting as an central registry for other actors.
-
-This way, we can start as many actors as we like, at any time.
+#### Registry
 
-We'll name this module `network_actor_registry.py` to be used later.
+In order to help distributed actor visibility while enabling more advance
+patterns, a centralized actor registry can be implemented.
 
+Example (`network_actor_registry.py`, server and library):
 ```python
 import itertools
 import os
@@ -220,8 +204,9 @@ if __name__ == '__main__':
             time.sleep(10)
 ```
 
-Using registry also allow us to register new actors dynamically.
+Using a registry also allow us to register new actors dynamically.
 
+Example (remote actor registration):
 ```python
 import time
 
@@ -241,9 +226,10 @@ with Registry.connect(address) as registry:
 ```
 
 And we can access those actors by retrieving their addresses from
-the registry (taking care of handling local addresses, see
-[forwarded proxies](#forwarded-proxies)).
+the registry (keep in mind you would still need to translate local addresses,
+see [forwarded proxies](#forwarded-proxies)).
 
+Exemple (actor registry usage):
 ```python
 from network_actor_registry import Registry, NetworkActor
 
@@ -255,3 +241,84 @@ with Registry.connect(address, b'SECRET') as registry:
         with NetworkActor.connect(address, b'SECRET') as actor:
             print(f'Actor at port {port} has pid {actor.getpid()}')
 ```
+
+#### Autodiscovery
+
+By using [zeroconf] to provide Multicast DNS Service Discovery,
+we can easily publish `uactor.Actor` processes across the network, without
+the need of any centralized registry.
+
+Example (`network_actor_zeroconf.py`, server and library):
+```python
+import os
+import socket
+import time
+import uactor
+import zeroconf
+
+class NetworkActor(uactor.Actor):
+
+    # Actor manager options to listen over TCP on a random port
+    _options_ = {'address': ('0.0.0.0', 0), 'authkey': b'SECRET'}
+
+    def getpid(self):
+        return os.getpid()
+
+if __name__ == '__main__':
+    zc = zeroconf.Zeroconf()
+    try:
+        with NetworkActor() as actor:
+            host, port = actor.connection_address
+            zc.register_service(
+                zeroconf.ServiceInfo(
+                    '_uactor._tcp.local.',
+                    'NetworkActor._uactor._tcp.local.',
+                    addresses=[socket.inet_aton(host)],
+                    port=port,
+                    server=f'{socket.gethostname()}.local.',
+                    )
+                )
+            while True:  # keep service alive
+                time.sleep(10)
+    finally:
+      zc.close()
+```
+
+And this would be a service relying on zeroconf to fetch the actor address.
+
+Example:
+```python
+import socket
+import threading
+import zeroconf
+
+from network_actor_zeroconf import NetworkActor
+
+class MyListener:
+    def __init__(self):
+        self.discovery = threading.Event()
+
+    def remove_service(self, zeroconf, type, name):
+        print(f'Service {name} removed')
+
+    def add_service(self, zeroconf, type, name):
+        info = zeroconf.get_service_info(type, name)
+        address = socket.inet_ntoa(info.addresses[0]), info.port
+
+        with NetworkActor.connect(address, b'SECRET') as actor:
+            pid = actor.getpid()
+            print(f'Service {name} discovered, actor.getpid returned {pid}')
+        self.discovery.set()
+
+try:
+    zc = zeroconf.Zeroconf()
+    listener = MyListener()
+    zeroconf.ServiceBrowser(zc, '_uactor._tcp.local.', listener)
+    listener.discovery.wait(10)
+    # Service NetworkActor._uactor._tcp.local. discovered, actor.getpid returned 906151
+finally:
+    zc.close()
+```
+
+[proxy-forwarding]: ./result_proxies.md#proxy-forwarding
+[zeroconf]: https://pypi.org/project/zeroconf/
diff --git a/examples/performance.md b/examples/performance.md
index 098df4f..f24d697 100644
--- a/examples/performance.md
+++ b/examples/performance.md
@@ -1,9 +1,9 @@
 # Performance tips
 
-Python is not the faster platform out there, and
-[inter-process communication][ipc] suffer of both serialization
-and data transfer overhead, but there is always somethign we can do
-to alleviate this.
+Python is not the fastest platform out there, and
+[inter-process communication][ipc] suffers of both serialization
+and data transfer overhead, but some considerations will let you avoid
+common performance hogs.
 
 ## Simplify serialized data
 
@@ -18,13 +18,22 @@ should consider implementing some [pickle] serialization
 [interfaces][pickle-reduce] in order to customize how they will be serialized,
 this way unnecessary data can be striped out.
 
-## Using external storage for big data-streams
+## Class optimization
 
-Sometimes, actors need to transfer huge loads of data between them.
+By defining the `__slots__` magic property on your classes (and by not
+adding `__dict__` to it), their property mapping becomes immutable,
+dramatically reducing their instancing cost.
 
-Message-passing protocols are usually not the best at this, but you
-can storing that data somewhere else while only sending, as the message,
-what's necessary to externally retrieve that data from there.
+Keep in mind, if you plan to [weakref] those instances, you'll need to add
+`__weakref__` to `__slots__`.
+
+## External storage for big data-streams
+
+Sometimes, actors need to transfer huge data blobs of data between them.
+
+Message-passing protocols are usually not the best at this, it is better to
+persistently store that data somewhere else while only sending, as the message,
+what's necessary to externally retrieve that data.
 
 You can see how to achieve this in our
 [Intermediate result storage](./result_storage.md) section.
@@ -32,18 +41,17 @@ You can see how to achieve this in our
 ## Pickle5 (hack)
 
 Traditionally, [multiprocessing], and more specifically [pickle],
-were not specially optimized to transfer binary data buffers.
+were not particularly optimized for binary data buffer transmission.
 
-Python 3.8 introduced the new pickle protocol in [PEP 574][pep574],
+Python 3.8 introduced a new pickle protocol ([PEP 574][pep574]),
 greatly optimizing the serialization of [buffer] objects
-(like [bytearray], [memoryview], [numpy.ndarray]) by implementing a
-dedicated code path.
+(like [bytearray], [memoryview], [numpy.ndarray]).
 
 For compatibility reasons, [multiprocessing] does not use the latest
-pickle protocol available, and does not expose any option to change that
+pickle protocol available, and it does not expose any way of doing so
 other than patching it globally.
 
-Here is a patch for Python 3.8 to use the latest pickle protocol available.
+Workaround (tested on CPython 3.8 and 3.9, to use the latest protocol):
 ```python
 import multiprocessing.connection as mpc
 
@@ -55,17 +63,17 @@ class ForkingPickler5(mpc._ForkingPickler):
 mpc._ForkingPickler = ForkingPickler5
 ```
 
-For previous python versions, a [pickle5 backport][pickle5] is available, but
-the patch turns out a bit messier because of how tightly coupled the
-[multiprocessing] implementation is.
+For previous CPython versions, a [pickle5 backport][pickle5] is available, but
+the patch turns out a bit messier because of implementation details.
 
+Workaround (tested on CPython 3.6 and 3.7, to use the pickle5 backport):
 ```python
 import io
 import multiprocessing.connection as mpc
 import pickle5
 
 class ForkingPickler5(pickle5.Pickler):
-    upstream = mpc._ForkingPickler
+    wrapped = mpc._ForkingPickler
     loads = staticmethod(pickle5.loads)
 
     @classmethod
@@ -75,20 +83,19 @@ class ForkingPickler5(pickle5.Pickler):
         return buf.getbuffer()
 
     def __init__(self, file, protocol=-1, **kwargs):
-        self.dispatch_table = (
-            self
-            .upstream(file, protocol, **kwargs)
-            .dispatch_table
-            )
         super().__init__(file, protocol, **kwargs)
+        self.dispatch_table = \
+          self.wrapped(file, protocol, **kwargs).dispatch_table
 
 mpc._ForkingPickler = ForkingPickler5
 ```
 
-Keep in mind this is no more than a dirty hack, is opt to the implementor
-to apply these patches at his own discretion.
+Keep in mind these snippets are no more than dirty workarounds to one of many
+[multiprocessing][multiprocessing] implementation issues, so use this code
+with caution.
 
 [ipc]: https://en.wikipedia.org/wiki/Inter-process_communication
+[weakref]: https://docs.python.org/3/library/weakref.html
 [pep574]: https://www.python.org/dev/peps/pep-0574/
 [buffer]: https://docs.python.org/3/c-api/buffer.html
 [bytearray]: https://docs.python.org/3/library/functions.html#func-bytearray
diff --git a/examples/pool.md b/examples/pool.md
index f5037cd..a827097 100644
--- a/examples/pool.md
+++ b/examples/pool.md
@@ -1,19 +1,19 @@
 # Actor pool example
 
 As with every multiprocessing framework, the necessity of keeping track of
-many execution units (in our case, actors) is quite common with uActor.
+many workers (in our case, actors) also applies to **uActor**.
 
-Here's where object pools come to hand, allowing to keep track of many
-objects at the same time.
+Here's where actor pools come to hand, allowing to keep track of many
+of them at the same time, while enabling parallelization and load-balancing.
 
-Here we will explain some approaches on implementing actor pools,
-taking concurrency into consideration.
+
+## Client-side parallelization
 
-## Client parallelization
-
-Actor pool example where parallelization is achieved at the client side using
-threads, calling to a synchronous actor.
+By design, calling actor methods is a fully synchronous operation, and as such
+we can simply use a [ThreadPool] to offload waiting the result into threads
+for very cheap, and exposing [AsyncResult] objects.
 
+Example:
 ```python
 import os
 import itertools
@@ -58,9 +58,16 @@ with AsyncActorPool(4, SyncActor) as pool:
 
 ## Actor asynchronous results
 
-Actor pool example where parallelization is performed on the actor side,
-returning `AsyncResult` proxies (see [result proxies](./result_proxies.md)).
+**uActor** (because of multiprocessing [SyncManager]) supports proxying
+[AsyncResult] objects  (see [result proxies](./result_proxies.md)), so we
+might think putting a [ThreadPool] into the actor process and return
+[AsyncResult] proxies via actor methods, greatly simplifying client code.
+
+**Important:** this example, while useful, is way more expensive than the above
+[client-side parallelization](#client-side-parallelization) implementation
+(around 24 times in my testing) as proxying [AsyncResult] is relatively costly.
 
+Example:
 ```python
 import os
 import itertools
@@ -99,3 +106,7 @@ with SyncActorPool(4, AsyncActor) as pool:
     results = [pool.call('getpid') for _ in range(5)]
     print([result.get() for result in results])
 ```
+
+[AsyncResult]: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.AsyncResult
+[ThreadPool]: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.ThreadPool
+[SyncManager]: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.managers.SyncManager
diff --git a/examples/result_proxies.md b/examples/result_proxies.md
index 00c0531..ee61f90 100644
--- a/examples/result_proxies.md
+++ b/examples/result_proxies.md
@@ -12,6 +12,7 @@ supporting different use-cases:
   These proxies will only be functional when received by the main process or
   other actors.
 
+Example:
 ```python
 import uactor
 
@@ -46,6 +47,7 @@ different proxies or values.
 When `uactor.proxy` is called, a new proxy is created for the given value
 and typeid, which can be transferred safely to other processes.
 
+Example:
 ```python
 import uactor
 
@@ -70,12 +72,13 @@ with Actor() as actor:
 
 ## Synchronization proxies
 
-uActor makes quite easy to share synchronization primitives between processes,
+**uActor** enables easily sharing synchronization primitives between processes,
 by including specific proxies for this such as `Event`, `Lock`, `RLock`,
 `Semaphore`, `BoundedSemaphore`, `Condition` and `Barrier`, which can be
 used with primitives from `threading`, or even `multiprocessing` (albeit
-using proxies to `multiprocessing` should be avoided):
+using proxies to `multiprocessing` should be avoided).
 
+Example:
 ```python
 import threading
 import uactor
@@ -102,12 +105,13 @@ with Actor() as actor:
 
 ## Asynchronous proxies
 
-uActor includes those extremely useful `Pool` and `AsyncResult` (for
+**uActor** includes those extremely useful `Pool` and `AsyncResult` (for
 (for `multiprocessing.pool.Pool`) and `Queue` (for `queue.Queue`) proxies.
 
 This allow to parallelize work across multiple actors way easier than using
 raw primitives, just by sharing asynchronous result objects or queues.
 
+Example:
 ```python
 import time
 import multiprocessing.pool
@@ -133,3 +137,69 @@ with Actor() as actor:
     print(f'{round(time.time() - start, 4)}s')
     # 2.0032s
 ```
+
+
+## Proxy forwarding
+
+Another neat feature from **uActor** is proxy forwarding, that is, being able
+to pass proxies as arguments or return them, to and from different actors.
+
+When explicitly setting an `authkey` on an actor (via its `_options_`) or
+when manually connecting to a remote proxy (via `uactor.Actor.connect`),
+their owned proxies will raise an `AuthkeyError` when forwarded if the
+caller process isn't already connected to that specific actor.
+
+Example (`proxy_forwarding.py`, library):
+```python
+import uactor
+
+class MyActor(uactor.Actor):
+    _exposed_ = ('my_other_actor', 'my_other_actor_address')
+
+    def __init__(self):
+        self.my_other_actor = MyOtherActor()
+
+    @property
+    def my_other_actor_address(self):
+      return self.my_other_actor.connection_address
+
+class MyOtherActor(uactor.Actor):
+    _options_ = {'authkey': b'OtherSecret'}
+
+```
+
+Example (raising `AuthkeyError` with a remote actor):
+```python
+from proxy_forwarding import MyActor
+
+with MyActor() as actor:
+    my_other_actor = actor.my_other_actor
+    # AuthKeyError
+```
+
+In this case, we need to connect to actors before being able to handle their
+proxies, as its `authkey` must be defined beforehand.
+
+Example:
+```python
+from proxy_forwarding import MyActor
+
+with MyActor() as actor:
+    address = actor.my_other_actor_address
+    with MyOtherActor.connect(address, b'OtherSecret'):
+        my_other_actor = actor.my_other_actor
+```
+
+Alternatively, we can opt to perform this connection only as a fallback via
+exception handling.
+
+Example:
+```python
+with MyActor() as actor:
+    try:
+        my_other_actor = actor.my_other_actor
+    except uactor.AuthKeyError as e:
+        address = actor.my_other_actor_address
+        with MyOtherActor.connect(address, b'OtherSecret'):
+            my_other_actor = actor.my_other_actor
+```
diff --git a/examples/result_storage.md b/examples/result_storage.md
index 6430388..007b26f 100644
--- a/examples/result_storage.md
+++ b/examples/result_storage.md
@@ -13,11 +13,12 @@ Starting from Python 3.8, [shared memory][shared-memory] enables
 multiple processes (running in the same system) to read and write
 to the same memory [buffer].
 
-Shared memory is exposed as a buffer we can read and write to,
+Shared memory is exposed as a buffer we can later read and write to,
 and as such we can use it as a transport for our data.
 
-This feature plays quite nicely with uActor actors.
+This feature plays quite nicely with **uActor** actors.
 
+Example:
 ```python
 import os
 import multiprocessing.managers
@@ -49,7 +50,7 @@ with SharedActor() as actor:
 ```
 
 Any kind of structured data, not just bytes, can be transferred this way
-using any [serialization] library such as [pickle].
+as long it is [serialized][serialization] like as using [pickle].
 
 ## External data stores
 
diff --git a/examples/stick.md b/examples/stick.md
index 7951b19..33c84d9 100644
--- a/examples/stick.md
+++ b/examples/stick.md
@@ -5,11 +5,12 @@ When handling multi-process concurrency, your operative system
 the workload between processes at its best.
 
 But, when looking for maximum performance, we may want to prevent two actors
-to run in the same CPU core, otherwise they have to share processing time.
+from running in the same CPU thread, share processing time.
 
 Thanks to the awesome [psutil][psutil] library we can do this simply by
 selecting an specific CPU core per process.
 
+Example:
 ```python
 import psutil
 import uactor
diff --git a/setup.py b/setup.py
index 22d1253..a9d44c8 100644
--- a/setup.py
+++ b/setup.py
@@ -18,8 +18,8 @@ MIT License (see `LICENSE`_).
 """
 
 import datetime
-import pathlib
 import os
+import pathlib
 import re
 
 from setuptools import setup
-- 
GitLab


From 0cefef8aaed6e61b55019fdd56c60b13417537ea Mon Sep 17 00:00:00 2001
From: ergoithz 
Date: Tue, 5 Jan 2021 21:56:49 +0000
Subject: [PATCH 7/7] more doc fixes

---
 examples/callbacks.md      |  4 ----
 examples/lifetime.md       |  2 +-
 examples/networking.md     | 35 +++++++++++++++++++----------------
 examples/performance.md    | 28 ++++++++++++++--------------
 examples/result_storage.md | 14 ++++++++------
 5 files changed, 42 insertions(+), 41 deletions(-)

diff --git a/examples/callbacks.md b/examples/callbacks.md
index c6cdc16..f0ceb26 100644
--- a/examples/callbacks.md
+++ b/examples/callbacks.md
@@ -5,9 +5,6 @@ a method call as parameter of another one. This is called callback, and can
 be used in many contexts to avoid blocking the main application process
 while waiting for results.
 
-This can be very useful when used along with asynchronous
-[result proxies](./result_proxies.md).
-
 Example:
 ```python
 import uactor
@@ -22,7 +19,6 @@ class ActorB(uactor.Actor):
     def receive(self, value):
         return 'pong' if value == 'ping' else 'error'
 
-
 actor_a = ActorA()
 actor_b = ActorB()
 print(actor_a.send(actor_b.receive))
diff --git a/examples/lifetime.md b/examples/lifetime.md
index aa1aa6f..90764fc 100644
--- a/examples/lifetime.md
+++ b/examples/lifetime.md
@@ -41,6 +41,6 @@ actor.shutdown()
 
 If you forget to manually finish the actor, don't worry, actor processes
 will be also finished when all their proxies get garbage-collected
-on its parent process.
+on its parent process, avoiding leaks.
 
 [context-managers]: https://docs.python.org/3/reference/datamodel.html#context-managers
diff --git a/examples/networking.md b/examples/networking.md
index 0b3c3c1..80f502e 100644
--- a/examples/networking.md
+++ b/examples/networking.md
@@ -3,13 +3,13 @@
 Actors will default to the most efficient method of inter-process
 communication available, which often relies on local sockets or pipes.
 
-Alternatively, actors can listen to an TCP/IP address and distributed
+Alternatively, actors be set to listen to TCP/IP interfaces and distributed
 across different machines over networks, rendering **uActor** as a great
 tool for distributed computing.
 
-Keep in mind that, when following this approach, due object serialization,
-actors classes (and any serialized type) still have to be available
-at the same importable module names, in all the involved parties.
+Please note when following this approach, due object serialization,
+actor classes (and any other serialized type) are required to be available
+at the same import locations, in all the involved parties.
 
 #### Connection
 
@@ -140,9 +140,9 @@ if __name__ == '__main__':
             pass
 ```
 
-This way, a remote proxy will be able to end the mainloop by calling
-shutdown and end the owner process mainloop, effectively finishing
-the process. We'll import the actor class from the previous example.
+The code above will enable remote proxies to break the mainloop by calling
+shutdown, exiting the actor context and effectively finishing both parent
+and actor processes.
 
 Example:
 ```python
@@ -265,10 +265,10 @@ class NetworkActor(uactor.Actor):
         return os.getpid()
 
 if __name__ == '__main__':
-    zc = zeroconf.Zeroconf()
-    try:
-        with NetworkActor() as actor:
-            host, port = actor.connection_address
+    with NetworkActor() as actor:
+        host, port = actor.connection_address
+        zc = zeroconf.Zeroconf()
+        try:
             zc.register_service(
                 zeroconf.ServiceInfo(
                     '_uactor._tcp.local.',
@@ -280,8 +280,8 @@ if __name__ == '__main__':
                 )
             while True:  # keep service alive
                 time.sleep(10)
-    finally:
-      zc.close()
+        finally:
+            zc.close()
 ```
 
 And this would be a service relying on zeroconf to fetch the actor address.
@@ -302,12 +302,16 @@ class MyListener:
         print(f'Service {name} removed')
 
     def add_service(self, zeroconf, type, name):
+        print(f'Service {name} discovered')
+        # Service NetworkActor._uactor._tcp.local discovered
+
         info = zeroconf.get_service_info(type, name)
         address = socket.inet_ntoa(info.addresses[0]), info.port
 
         with NetworkActor.connect(address, b'SECRET') as actor:
-            pid = actor.getpid()
-            print(f'Service {name} discovered, actor.getpid returned {pid}')
+            print(f'NetworkActor.getpid returned {actor.getpid()}')
+            # NetworkActor.getpid returned 906151
+
         self.discovery.set()
 
 try:
@@ -315,7 +319,6 @@ try:
     listener = MyListener()
     zeroconf.ServiceBrowser(zc, '_uactor._tcp.local.', listener)
     listener.discovery.wait(10)
-    # Service NetworkActor._uactor._tcp.local. discovered, actor.getpid returned 906151
 finally:
     zc.close()
 ```
diff --git a/examples/performance.md b/examples/performance.md
index f24d697..78d8000 100644
--- a/examples/performance.md
+++ b/examples/performance.md
@@ -1,9 +1,9 @@
 # Performance tips
 
-Python is not the fastest platform out there, and
+CPython is not the fastest interpreter out there, and
 [inter-process communication][ipc] suffers of both serialization
-and data transfer overhead, but some considerations will let you avoid
-common performance hogs.
+and data transfer overhead, but these considerations will help you avoid
+common performance pitfalls.
 
 ## Simplify serialized data
 
@@ -13,27 +13,27 @@ unnecessary data.
 
 ## Custom serialization
 
-When defining your own classes aimed to be sent to and from actors, you
-should consider implementing some [pickle] serialization
-[interfaces][pickle-reduce] in order to customize how they will be serialized,
-this way unnecessary data can be striped out.
+When defining your own classes aimed to be sent to and from actors,
+consider implementing some [pickle] serialization [interfaces][pickle-reduce]
+in order to customize how they will be serialized, so unnecessary state
+data will be ignored.
 
 ## Class optimization
 
 By defining the `__slots__` magic property on your classes (and by not
-adding `__dict__` to it), their property mapping becomes immutable,
-dramatically reducing their instancing cost.
+adding `__dict__` to it), their property mapping will become immutable,
+dramatically reducing their initialization cost.
 
-Keep in mind, if you plan to [weakref] those instances, you'll need to add
+**Tip:** if you plan to [weakref] those instances, you'll need to add
 `__weakref__` to `__slots__`.
 
 ## External storage for big data-streams
 
-Sometimes, actors need to transfer huge data blobs of data between them.
+In some cases, actors might need to transfer huge data blobs of between them.
 
-Message-passing protocols are usually not the best at this, it is better to
-persistently store that data somewhere else while only sending, as the message,
-what's necessary to externally retrieve that data.
+In general, message-passing protocols are usually not the best at this,
+it might be better to persistently store that data somewhere else while only
+sending, as the message, what's necessary to externally fetch that data.
 
 You can see how to achieve this in our
 [Intermediate result storage](./result_storage.md) section.
diff --git a/examples/result_storage.md b/examples/result_storage.md
index 007b26f..9378f93 100644
--- a/examples/result_storage.md
+++ b/examples/result_storage.md
@@ -58,18 +58,19 @@ Using a centralized broker where all processes can concurrently store and
 retrieve data can be considered, specially when distributing actors over the
 network.
 
-Some in-memory databases such as [memcached] or [redis] are specially good at
-storing temporary data, but even traditional databases or [blob] storage
-services could be used.
+Some in-memory databases (such as [memcached] or [redis]) are specially good
+at storing temporary data, but even traditional databases or dedicated
+[blob] storage services (such as [min.io]) could be used, enabling those
+resources to be accessed [on actors accross the network](./networking.md).
 
-When talking about local-only tasks, [tempfile.NamedTemporaryFile] could
+When talking about local-only actors, [tempfile.NamedTemporaryFile] could
 be also a good option when used alongside RAM-based virtual filesystems like
 [tmpfs]. Regular filesystems should be only considered for objects
 being forwarded across many actors since even the fastest block device
-is still times slower than regular [multiprocessing] mechanisms.
+is still slower than regular [multiprocessing] mechanisms.
 
 Integrating with external services is a whole big subject on its own and
-falls outside this document scope.
+its outside this documentation scope.
 
 [shared-memory]: https://docs.python.org/3/library/multiprocessing.shared_memory.html
 [pickle]: https://docs.python.org/3/library/pickle.html
@@ -79,5 +80,6 @@ falls outside this document scope.
 [serialization]: https://en.wikipedia.org/wiki/Serialization
 [memcached]: https://www.memcached.org/
 [redis]: https://redis.io/
+[min.io]: https://github.com/minio/minio
 [blob]: https://en.wikipedia.org/wiki/Binary_large_object
 [buffer]: https://docs.python.org/3/c-api/buffer.html
-- 
GitLab