Repository: abhinavsingh/proxy.py
Pull request #1051 (2022-01-23 21:16:15)

[Work] `kwargs` independent `work_klass` creation and work core
By depending upon `kwargs` work core had to depend upon `conn`, `addr` and other tcp connection related keywords

codecov[bot]: # [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) Report
> Merging [#1051](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (4114ff0) into [develop](https://codecov.io/gh/abhinavsingh/proxy.py/commit/b49fcbddc9a03e939baaa0718bcf97d477da06c1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) (b49fcbd) will **decrease** coverage by `0.15%`.
> The diff coverage is `65.38%`.

[![Impacted file tree graph](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/graphs/tree.svg?width=650&height=150&src=pr&token=Zh9J7b4la2&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)

```diff
@@             Coverage Diff             @@
##           develop    #1051      +/-   ##
===========================================
- Coverage    86.89%   86.73%   -0.16%     
===========================================
  Files          160      160              
  Lines         6647     6659      +12     
  Branches       673      674       +1     
===========================================
  Hits          5776     5776              
- Misses         756      768      +12     
  Partials       115      115              
```

| Flag | Coverage Δ | |
|---|---|---|
| pytest | `86.55% <65.38%> (-0.16%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | Coverage Δ | |
|---|---|---|
| [proxy/core/base/tcp\_tunnel.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9iYXNlL3RjcF90dW5uZWwucHk=) | `39.62% <ø> (ø)` | |
| [proxy/core/work/threaded.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL3RocmVhZGVkLnB5) | `100.00% <ø> (ø)` | |
| [proxy/http/handler.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvaHR0cC9oYW5kbGVyLnB5) | `75.44% <ø> (ø)` | |
| [proxy/core/work/local.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL2xvY2FsLnB5) | `52.00% <33.33%> (-10.50%)` | :arrow_down: |
| [proxy/core/work/remote.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL3JlbW90ZS5weQ==) | `57.14% <33.33%> (-3.97%)` | :arrow_down: |
| [proxy/socks/handler.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvc29ja3MvaGFuZGxlci5weQ==) | `83.33% <50.00%> (ø)` | |
| [proxy/core/connection/pool.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9jb25uZWN0aW9uL3Bvb2wucHk=) | `94.28% <100.00%> (ø)` | |
| [proxy/core/work/fd/fd.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL2ZkL2ZkLnB5) | `87.50% <100.00%> (ø)` | |
| [proxy/core/work/fd/local.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL2ZkL2xvY2FsLnB5) | `100.00% <100.00%> (ø)` | |
| [proxy/core/work/fd/remote.py](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS93b3JrL2ZkL3JlbW90ZS5weQ==) | `96.15% <100.00%> (ø)` | |
| ... and [6 more](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Last update [b49fcbd...4114ff0](https://codecov.io/gh/abhinavsingh/proxy.py/pull/1051?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).


Patch:
diff --git a/README.md b/README.md
index 8bfd444..273bb02 100644
--- a/README.md
+++ b/README.md
@@ -2265,8 +2265,8 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
           [--tunnel-username TUNNEL_USERNAME]
           [--tunnel-ssh-key TUNNEL_SSH_KEY]
           [--tunnel-ssh-key-passphrase TUNNEL_SSH_KEY_PASSPHRASE]
-          [--tunnel-remote-port TUNNEL_REMOTE_PORT] [--enable-events]
-          [--threadless] [--threaded] [--num-workers NUM_WORKERS]
+          [--tunnel-remote-port TUNNEL_REMOTE_PORT] [--threadless]
+          [--threaded] [--num-workers NUM_WORKERS] [--enable-events]
           [--local-executor LOCAL_EXECUTOR] [--backlog BACKLOG]
           [--hostname HOSTNAME] [--port PORT] [--ports PORTS [PORTS ...]]
           [--port-file PORT_FILE] [--unix-socket-path UNIX_SOCKET_PATH]
@@ -2294,7 +2294,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
           [--filtered-client-ips FILTERED_CLIENT_IPS]
           [--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]
 
-proxy.py v2.4.0rc8.dev7+g1871027
+proxy.py v2.4.0rc8.dev17+g59a4335.d20220123
 
 options:
   -h, --help            show this help message and exit
@@ -2313,9 +2313,6 @@ options:
   --tunnel-remote-port TUNNEL_REMOTE_PORT
                         Default: 8899. Remote port which will be forwarded
                         locally for proxy.
-  --enable-events       Default: False. Enables core to dispatch lifecycle
-                        events. Plugins can be used to subscribe for core
-                        events.
   --threadless          Default: True. Enabled by default on Python 3.8+ (mac,
                         linux). When disabled a new thread is spawned to
                         handle each client connection.
@@ -2324,6 +2321,9 @@ options:
                         handle each client connection.
   --num-workers NUM_WORKERS
                         Defaults to number of CPU cores.
+  --enable-events       Default: False. Enables core to dispatch lifecycle
+                        events. Plugins can be used to subscribe for core
+                        events.
   --local-executor LOCAL_EXECUTOR
                         Default: 1. Enabled by default. Use 0 to disable. When
                         enabled acceptors will make use of local (same
@@ -2422,8 +2422,9 @@ options:
                         Default: proxy.http.proxy.auth.AuthPlugin. Auth plugin
                         to use instead of default basic auth plugin.
   --cache-dir CACHE_DIR
-                        Default: A temporary directory. Flag only applicable
-                        when cache plugin is used with on-disk storage.
+                        Default: /Users/abhinavsingh/.proxy/cache. Flag only
+                        applicable when cache plugin is used with on-disk
+                        storage.
   --proxy-pool PROXY_POOL
                         List of upstream proxies to use in the pool
   --enable-web-server   Default: False. Whether to enable
diff --git a/examples/ssl_echo_server.py b/examples/ssl_echo_server.py
index 56a7d63..199bb0c 100644
--- a/examples/ssl_echo_server.py
+++ b/examples/ssl_echo_server.py
@@ -21,8 +21,8 @@ class EchoSSLServerHandler(BaseTcpServerHandler[TcpClientConnection]):
     """Wraps client socket during initialization."""
 
     @staticmethod
-    def create(**kwargs: Any) -> TcpClientConnection:   # pragma: no cover
-        return TcpClientConnection(**kwargs)
+    def create(*args: Any) -> TcpClientConnection:   # pragma: no cover
+        return TcpClientConnection(*args)
 
     def initialize(self) -> None:
         # Acceptors don't perform TLS handshake.  Perform the same
diff --git a/examples/task.py b/examples/task.py
new file mode 100644
index 0000000..aae6b26
--- /dev/null
+++ b/examples/task.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+"""
+    proxy.py
+    ~~~~~~~~
+    ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
+    Network monitoring, controls & Application development, testing, debugging.
+
+    :copyright: (c) 2013-present by Abhinav Singh and contributors.
+    :license: BSD, see LICENSE for more details.
+"""
+import time
+import argparse
+import threading
+import multiprocessing
+from typing import Any
+
+from proxy.core.work import (
+    Work, ThreadlessPool, BaseLocalExecutor, BaseRemoteExecutor,
+)
+from proxy.common.flag import FlagParser
+from proxy.common.backports import NonBlockingQueue
+
+
+class Task:
+    """This will be our work object."""
+
+    def __init__(self, payload: bytes) -> None:
+        self.payload = payload
+        print(payload)
+
+
+class TaskWork(Work[Task]):
+    """This will be our handler class, created for each received work."""
+
+    @staticmethod
+    def create(*args: Any) -> Task:
+        """Work core doesn't know how to create work objects for us, so
+        we must provide an implementation of create method here."""
+        return Task(*args)
+
+
+class LocalTaskExecutor(BaseLocalExecutor):
+    """We'll define a local executor which is capable of receiving
+    log lines over a non blocking queue."""
+
+    def work(self, *args: Any) -> None:
+        task_id = int(time.time())
+        uid = '%s-%s' % (self.iid, task_id)
+        self.works[task_id] = self.create(uid, *args)
+
+
+class RemoteTaskExecutor(BaseRemoteExecutor):
+
+    def work(self, *args: Any) -> None:
+        task_id = int(time.time())
+        uid = '%s-%s' % (self.iid, task_id)
+        self.works[task_id] = self.create(uid, *args)
+
+
+def start_local(flags: argparse.Namespace) -> None:
+    work_queue = NonBlockingQueue()
+    executor = LocalTaskExecutor(iid=1, work_queue=work_queue, flags=flags)
+
+    t = threading.Thread(target=executor.run)
+    t.daemon = True
+    t.start()
+
+    try:
+        i = 0
+        while True:
+            work_queue.put(('%d' % i).encode('utf-8'))
+            i += 1
+    except KeyboardInterrupt:
+        pass
+    finally:
+        executor.running.set()
+        t.join()
+
+
+def start_remote(flags: argparse.Namespace) -> None:
+    pipe = multiprocessing.Pipe()
+    work_queue = pipe[0]
+    executor = RemoteTaskExecutor(iid=1, work_queue=pipe[1], flags=flags)
+
+    p = multiprocessing.Process(target=executor.run)
+    p.daemon = True
+    p.start()
+
+    try:
+        i = 0
+        while True:
+            work_queue.send(('%d' % i).encode('utf-8'))
+            i += 1
+    except KeyboardInterrupt:
+        pass
+    finally:
+        executor.running.set()
+        p.join()
+
+
+def start_remote_pool(flags: argparse.Namespace) -> None:
+    with ThreadlessPool(flags=flags, executor_klass=RemoteTaskExecutor) as pool:
+        try:
+            i = 0
+            while True:
+                work_queue = pool.work_queues[i % flags.num_workers]
+                work_queue.send(('%d' % i).encode('utf-8'))
+                i += 1
+        except KeyboardInterrupt:
+            pass
+
+
+if __name__ == '__main__':
+    flags = FlagParser.initialize(
+        ['--disable-http-proxy'],
+        work_klass=TaskWork,
+    )
+    start_remote_pool(flags)
+    # start_remote(flags)
+    # start_local(flags)
diff --git a/examples/tcp_echo_server.py b/examples/tcp_echo_server.py
index 5dddd63..b0fd70b 100644
--- a/examples/tcp_echo_server.py
+++ b/examples/tcp_echo_server.py
@@ -20,8 +20,8 @@ class EchoServerHandler(BaseTcpServerHandler[TcpClientConnection]):
     """Sets client socket to non-blocking during initialization."""
 
     @staticmethod
-    def create(**kwargs: Any) -> TcpClientConnection:   # pragma: no cover
-        return TcpClientConnection(**kwargs)
+    def create(*args: Any) -> TcpClientConnection:   # pragma: no cover
+        return TcpClientConnection(*args)
 
     def initialize(self) -> None:
         self.work.connection.setblocking(False)
diff --git a/proxy/core/base/tcp_tunnel.py b/proxy/core/base/tcp_tunnel.py
index 4844438..fedeac2 100644
--- a/proxy/core/base/tcp_tunnel.py
+++ b/proxy/core/base/tcp_tunnel.py
@@ -47,8 +47,8 @@ class BaseTcpTunnelHandler(BaseTcpServerHandler[TcpClientConnection]):
         pass    # pragma: no cover
 
     @staticmethod
-    def create(**kwargs: Any) -> TcpClientConnection:   # pragma: no cover
-        return TcpClientConnection(**kwargs)
+    def create(*args: Any) -> TcpClientConnection:   # pragma: no cover
+        return TcpClientConnection(*args)
 
     def initialize(self) -> None:
         self.work.connection.setblocking(False)
diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py
index 2536c5b..482dc19 100644
--- a/proxy/core/connection/pool.py
+++ b/proxy/core/connection/pool.py
@@ -76,8 +76,8 @@ class UpstreamConnectionPool(Work[TcpServerConnection]):
         self.pools: Dict[HostPort, Set[TcpServerConnection]] = {}
 
     @staticmethod
-    def create(**kwargs: Any) -> TcpServerConnection:   # pragma: no cover
-        return TcpServerConnection(**kwargs)
+    def create(*args: Any) -> TcpServerConnection:   # pragma: no cover
+        return TcpServerConnection(*args)
 
     def acquire(self, addr: HostPort) -> Tuple[bool, TcpServerConnection]:
         """Returns a reusable connection from the pool.
@@ -154,7 +154,7 @@ class UpstreamConnectionPool(Work[TcpServerConnection]):
 
         NOTE: You must not use the returned connection, instead use `acquire`.
         """
-        new_conn = self.create(host=addr[0], port=addr[1])
+        new_conn = self.create(addr[0], addr[1])
         new_conn.connect()
         self._add(new_conn)
         logger.debug(
diff --git a/proxy/core/work/fd/fd.py b/proxy/core/work/fd/fd.py
index 71a52af..09a46a2 100644
--- a/proxy/core/work/fd/fd.py
+++ b/proxy/core/work/fd/fd.py
@@ -26,26 +26,16 @@ class ThreadlessFdExecutor(Threadless[T]):
     """A threadless executor which handles file descriptors
     and works with read/write events over a socket."""
 
-    def work(self, **kwargs: Any) -> None:
-        fileno: int = kwargs['fileno']
-        addr: Optional[HostPort] = kwargs.get('addr', None)
-        conn: Optional[TcpOrTlsSocket] = \
-            kwargs.get('conn', None)
+    def work(self, *args: Any) -> None:
+        fileno: int = args[0]
+        addr: Optional[HostPort] = args[1]
+        conn: Optional[TcpOrTlsSocket] = args[2]
         conn = conn or socket.fromfd(
             fileno, family=socket.AF_INET if self.flags.hostname.version == 4 else socket.AF_INET6,
             type=socket.SOCK_STREAM,
         )
         uid = '%s-%s-%s' % (self.iid, self._total, fileno)
-        self.works[fileno] = self.flags.work_klass(
-            self.flags.work_klass.create(
-                conn=conn,
-                addr=addr,
-            ),
-            flags=self.flags,
-            event_queue=self.event_queue,
-            uid=uid,
-            upstream_conn_pool=self._upstream_conn_pool,
-        )
+        self.works[fileno] = self.create(uid, conn, addr)
         self.works[fileno].publish_event(
             event_name=eventNames.WORK_STARTED,
             event_payload={'fileno': fileno, 'addr': addr},
diff --git a/proxy/core/work/fd/local.py b/proxy/core/work/fd/local.py
index 0c62d30..65fae31 100644
--- a/proxy/core/work/fd/local.py
+++ b/proxy/core/work/fd/local.py
@@ -47,4 +47,4 @@ class LocalFdExecutor(ThreadlessFdExecutor[NonBlockingQueue]):
         # NOTE: Here we are assuming to receive a connection object
         # and not a fileno because we are a LocalExecutor.
         fileno = conn.fileno()
-        self.work(fileno=fileno, addr=addr, conn=conn)
+        self.work(fileno, addr, conn)
diff --git a/proxy/core/work/fd/remote.py b/proxy/core/work/fd/remote.py
index f7ad87a..fdad0ab 100644
--- a/proxy/core/work/fd/remote.py
+++ b/proxy/core/work/fd/remote.py
@@ -43,7 +43,7 @@ class RemoteFdExecutor(ThreadlessFdExecutor[connection.Connection]):
         if not self.flags.unix_socket_path:
             addr = self.work_queue.recv()
         fileno = recv_handle(self.work_queue)
-        self.work(fileno=fileno, addr=addr)
+        self.work(fileno, addr, None)
         return False
 
     def work_queue_fileno(self) -> Optional[int]:
diff --git a/proxy/core/work/local.py b/proxy/core/work/local.py
index 9a72fab..0745e81 100644
--- a/proxy/core/work/local.py
+++ b/proxy/core/work/local.py
@@ -8,7 +8,9 @@
     :copyright: (c) 2013-present by Abhinav Singh and contributors.
     :license: BSD, see LICENSE for more details.
 """
+import queue
 import asyncio
+import contextlib
 from typing import Any, Optional
 
 from .threadless import Threadless
@@ -30,3 +32,11 @@ class BaseLocalExecutor(Threadless[NonBlockingQueue]):
 
     def work_queue_fileno(self) -> Optional[int]:
         return None
+
+    def receive_from_work_queue(self) -> bool:
+        with contextlib.suppress(queue.Empty):
+            work = self.work_queue.get()
+            if isinstance(work, bool) and work is False:
+                return True
+            self.work(work)
+        return False
diff --git a/proxy/core/work/remote.py b/proxy/core/work/remote.py
index 33d15af..afac2eb 100644
--- a/proxy/core/work/remote.py
+++ b/proxy/core/work/remote.py
@@ -33,3 +33,7 @@ class BaseRemoteExecutor(Threadless[connection.Connection]):
 
     def close_work_queue(self) -> None:
         self.work_queue.close()
+
+    def receive_from_work_queue(self) -> bool:
+        self.work(self.work_queue.recv())
+        return False
diff --git a/proxy/core/work/threaded.py b/proxy/core/work/threaded.py
index 4437424..74899e8 100644
--- a/proxy/core/work/threaded.py
+++ b/proxy/core/work/threaded.py
@@ -33,7 +33,7 @@ def start_threaded_work(
 ) -> Tuple['Work[T]', threading.Thread]:
     """Utility method to start a work in a new thread."""
     work = flags.work_klass(
-        flags.work_klass.create(conn=conn, addr=addr),
+        flags.work_klass.create(conn, addr),
         flags=flags,
         event_queue=event_queue,
         upstream_conn_pool=None,
diff --git a/proxy/core/work/threadless.py b/proxy/core/work/threadless.py
index 216a5bd..1ad84ac 100644
--- a/proxy/core/work/threadless.py
+++ b/proxy/core/work/threadless.py
@@ -17,6 +17,7 @@ import multiprocessing
 from abc import ABC, abstractmethod
 from typing import (
     TYPE_CHECKING, Any, Set, Dict, List, Tuple, Generic, TypeVar, Optional,
+    cast,
 )
 
 from ...common.types import Readables, Writables, SelectableEvents
@@ -120,9 +121,20 @@ class Threadless(ABC, Generic[T]):
         raise NotImplementedError()
 
     @abstractmethod
-    def work(self, **kwargs: Any) -> None:
+    def work(self, *args: Any) -> None:
         raise NotImplementedError()
 
+    def create(self, uid: str, *args: Any) -> 'Work[T]':
+        return cast(
+            'Work[T]', self.flags.work_klass(
+                self.flags.work_klass.create(*args),
+                flags=self.flags,
+                event_queue=self.event_queue,
+                uid=uid,
+                upstream_conn_pool=self._upstream_conn_pool,
+            ),
+        )
+
     def close_work_queue(self) -> None:
         """Only called if ``work_queue_fileno`` returns an integer.
         If an fd is select-able for work queue, make sure
diff --git a/proxy/core/work/work.py b/proxy/core/work/work.py
index 0cb4b67..d68969a 100644
--- a/proxy/core/work/work.py
+++ b/proxy/core/work/work.py
@@ -49,7 +49,7 @@ class Work(ABC, Generic[T]):
 
     @staticmethod
     @abstractmethod
-    def create(**kwargs: Any) -> T:
+    def create(*args: Any) -> T:
         """Implementations are responsible for creation of work objects
         from incoming args.  This helps keep work core agnostic to
         creation of externally defined work class objects."""
diff --git a/proxy/http/handler.py b/proxy/http/handler.py
index 04fd507..dc2d7cf 100644
--- a/proxy/http/handler.py
+++ b/proxy/http/handler.py
@@ -56,8 +56,8 @@ class HttpProtocolHandler(BaseTcpServerHandler[HttpClientConnection]):
     ##
 
     @staticmethod
-    def create(**kwargs: Any) -> HttpClientConnection:  # pragma: no cover
-        return HttpClientConnection(**kwargs)
+    def create(*args: Any) -> HttpClientConnection:  # pragma: no cover
+        return HttpClientConnection(*args)
 
     def initialize(self) -> None:
         super().initialize()
diff --git a/proxy/socks/handler.py b/proxy/socks/handler.py
index 82f56a4..87eb639 100644
--- a/proxy/socks/handler.py
+++ b/proxy/socks/handler.py
@@ -21,8 +21,8 @@ class SocksProtocolHandler(BaseTcpServerHandler[SocksClientConnection]):
         super().__init__(*args, **kwargs)
 
     @staticmethod
-    def create(**kwargs: Any) -> SocksClientConnection:
-        return SocksClientConnection(**kwargs)
+    def create(*args: Any) -> SocksClientConnection:
+        return SocksClientConnection(*args)
 
     def handle_data(self, data: memoryview) -> Optional[bool]:
         return super().handle_data(data)


Test patch:
diff --git a/tests/core/test_conn_pool.py b/tests/core/test_conn_pool.py
index 3918372..6547a0d 100644
--- a/tests/core/test_conn_pool.py
+++ b/tests/core/test_conn_pool.py
@@ -33,10 +33,7 @@ class TestConnectionPool(unittest.TestCase):
         mock_conn.closed = False
         # Acquire
         created, conn = pool.acquire(addr)
-        mock_tcp_server_connection.assert_called_once_with(
-            host=addr[0],
-            port=addr[1],
-        )
+        mock_tcp_server_connection.assert_called_once_with(addr[0], addr[1])
         mock_conn.mark_inuse.assert_called_once()
         mock_conn.reset.assert_not_called()
         self.assertTrue(created)
@@ -69,10 +66,7 @@ class TestConnectionPool(unittest.TestCase):
         # Acquire
         created, conn = pool.acquire(addr)
         self.assertTrue(created)
-        mock_tcp_server_connection.assert_called_once_with(
-            host=addr[0],
-            port=addr[1],
-        )
+        mock_tcp_server_connection.assert_called_once_with(addr[0], addr[1])
         self.assertEqual(conn, mock_conn)
         self.assertEqual(len(pool.pools[addr]), 1)
         self.assertTrue(conn in pool.pools[addr])
@@ -97,10 +91,7 @@ class TestConnectionPoolAsync:
         mock_conn = mock_tcp_server_connection.return_value
         addr = mock_conn.addr
         pool.add(addr)
-        mock_tcp_server_connection.assert_called_once_with(
-            host=addr[0],
-            port=addr[1],
-        )
+        mock_tcp_server_connection.assert_called_once_with(addr[0], addr[1])
         mock_conn.connect.assert_called_once()
         events = await pool.get_events()
         print(events)