Skip to content
This repository was archived by the owner on Nov 9, 2022. It is now read-only.

Commit 65fd535

Browse files
lamaralfacebook-github-bot
authored andcommitted
Enable Linux and BSD compatibility (#49)
Summary: Hey guys, first of all, great work on the framework. I need to use the framework on FreeBSD servers and it was quite a boomer when I saw it was depending on epoll, so I took some time and came up with this solution using `selectors` instead of `select`, that way things got OS agnostic and selectors should in theory use the most efficient method mechanism available. I took the care of also adjusting the `poll_mock()` in the tests. Please double check the work there as I was slightly unsure if that was the right way to go. Luiz Pull Request resolved: #49 Test Plan: Test logs for Linux and FreeBSD: #49 (comment) Reviewed By: pallotron Differential Revision: D29100368 Pulled By: skozlov404 fbshipit-source-id: d5b25392a8891deda6903e65989eef0525be759b
1 parent 8d21c08 commit 65fd535

File tree

4 files changed

+43
-20
lines changed

4 files changed

+43
-20
lines changed

.github/workflows/build.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ name: build
33
on: [push, pull_request]
44

55
jobs:
6-
build:
7-
6+
build-linux:
87
runs-on: ubuntu-latest
98
strategy:
109
matrix:
@@ -26,3 +25,24 @@ jobs:
2625
- name: Test
2726
run: |
2827
make test
28+
29+
build-bsd:
30+
runs-on: macos-latest
31+
strategy:
32+
matrix:
33+
python-version: [ '3.5', '3.6', '3.7', '3.8', '3.9' ]
34+
35+
steps:
36+
- uses: actions/checkout@v2
37+
- name: Set up Python ${{ matrix.python-version }}
38+
uses: actions/setup-python@v2
39+
with:
40+
python-version: ${{ matrix.python-version }}
41+
- name: Install dependencies and prepare for test
42+
run: |
43+
python -m pip install --upgrade pip
44+
python -m pip install setuptools --upgrade
45+
python -m pip install flake8
46+
- name: Test
47+
run: |
48+
make test

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ and you are good to go:
101101
# Requirements
102102

103103
* Linux (or any system that supports [`epoll`](http://linux.die.net/man/4/epoll))
104-
* Python 3.x
104+
* BSD (or any system that supports [`kqueue`](https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2))
105+
* Python 3.4+
105106

106107
# Installation
107108

fbtftp/base_server.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import collections
88
import ipaddress
99
import logging
10-
import select
10+
import selectors
1111
import socket
1212
import struct
1313
import threading
@@ -205,8 +205,8 @@ def __init__(
205205
self._listener = socket.socket(self._family, socket.SOCK_DGRAM)
206206
self._listener.setblocking(0) # non-blocking
207207
self._listener.bind((address, port))
208-
self._epoll = select.epoll()
209-
self._epoll.register(self._listener.fileno(), select.EPOLLIN)
208+
self._selector = selectors.DefaultSelector()
209+
self._selector.register(self._listener, selectors.EVENT_READ)
210210
self._should_stop = False
211211
self._server_stats = ServerStats(address, stats_interval_seconds)
212212
self._metrics_timer = None
@@ -226,7 +226,7 @@ def run(self, run_once=False):
226226
self.run_once()
227227
if run_once:
228228
break
229-
self._epoll.close()
229+
self._selector.close()
230230
self._listener.close()
231231
if self._metrics_timer is not None:
232232
self._metrics_timer.cancel()
@@ -271,11 +271,11 @@ def run_once(self):
271271
facility to know when data is ready to be retrived from the listening
272272
socket. See http://linux.die.net/man/4/epoll .
273273
"""
274-
events = self._epoll.poll()
275-
for fileno, eventmask in events:
276-
if not eventmask & select.EPOLLIN:
274+
events = self._selector.select()
275+
for key, mask in events:
276+
if not mask & selectors.EVENT_READ:
277277
continue
278-
if fileno == self._listener.fileno():
278+
if key.fd == self._listener.fileno():
279279
self.on_new_data()
280280
continue
281281

tests/base_server_test.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from fbtftp.base_server import BaseServer
1111

1212
MOCK_SOCKET_FILENO = 100
13-
SELECT_EPOLLIN = 1
13+
SELECTORS_EVENT_READ = 1
1414

1515

1616
class MockSocketListener:
@@ -72,14 +72,16 @@ def setUp(self):
7272
self.interval = 1
7373
self.network_queue = []
7474

75-
def poll_mock(self):
75+
def select_mock(self):
7676
"""
7777
mock the select.epoll.poll() method, returns an iterable containing a
7878
list of (fileno, eventmask), the fileno constant matches the
7979
MockSocketListener.fileno() method, eventmask matches select.EPOLLIN
8080
"""
8181
if len(self.network_queue) > 0:
82-
return [(MOCK_SOCKET_FILENO, SELECT_EPOLLIN)]
82+
obj = lambda: None
83+
obj.fd = MOCK_SOCKET_FILENO
84+
return [(obj, SELECTORS_EVENT_READ)]
8385
return []
8486

8587
def prepare_and_run(self, network_queue):
@@ -104,10 +106,10 @@ def prepare_and_run(self, network_queue):
104106
server._server_stats.increment_counter.assert_called_with("process_count")
105107
return server._handler
106108

107-
@patch("select.epoll")
108-
def testRRQ(self, epoll_mock):
109+
@patch("selectors.DefaultSelector")
110+
def testRRQ(self, selector_mock):
109111
# link the self.poll_mock() method with the select.epoll patched object
110-
epoll_mock.return_value.poll.side_effect = self.poll_mock
112+
selector_mock.return_value.select.side_effect = self.select_mock
111113
self.network_queue = [
112114
# RRQ + file name + mode + optname + optvalue
113115
b"\x00\x01some_file\x00binascii\x00opt1_key\x00opt1_val\x00"
@@ -171,10 +173,10 @@ def testCallbackException(self):
171173
stats_callback.side_effect = Exception("boom!")
172174
self.start_timer_and_wait_for_callback(stats_callback)
173175

174-
@patch("select.epoll")
175-
def testUnexpectedOpsCode(self, epoll_mock):
176+
@patch("selectors.DefaultSelector")
177+
def testUnexpectedOpsCode(self, selector_mock):
176178
# link the self.poll_mock() emthod with the select.epoll patched object
177-
epoll_mock.return_value.poll.side_effect = self.poll_mock
179+
selector_mock.return_value.select.side_effect = self.select_mock
178180
self.network_queue = [
179181
# RRQ + file name + mode + optname + optvalue
180182
b"\x00\xffsome_file\x00binascii\x00opt1_key\x00opt1_val\x00"

0 commit comments

Comments
 (0)