How a process release a redis lock which was not owned by this process? - redis-py

I tried to implement a simple read-preferred read-write lock using 2 mutexes (using redis.lock.Lock), like what is described in this link (https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)
In the [End Read] steps, I encountered this problem:
If b = 0, unlock g #(a write lock).
As, this READ process is not the one that acquired the lock, so the system throws an error. I think it has some token stored somewhere, and I can get it to use for the lock release, but I am not sure.
Could someone give me a hint? Thanks.
from enum import Enum
from redis import StrictRedis, lock
# data in Redis cache:
# "read_counter_lock_name" : 0
# "read_lock_lock_name" -> read lock, protect "read_counter_lock_name"
# "write_lock_lock_name" -> write lock, protect write data section
class Prefix(Enum):
READ = 'read_lock_'
WRITE = 'write_lock_'
READ_COUNTER = 'read_counter_'
class RedisLockParams(Enum):
TIMEOUT = 60 # maximum life for the lock in seconds = 60 seconds
SLEEP_TIME = 0.1 # the amount of time to sleep in seconds per loop iteration
# in redis lock's acquire() - sleep then retry
BLOCKING = True # acquire() should block until the lock has been acquired
BLOCKING_TIMEOUT = None # maximum amount of time in seconds to spend trying
# to acquire the lock
class ReadWriteLock:
def __init__(self, lock_name: str, redis_host: str, redis_port: int, redis_key: str):
self.__read_lock_name = Prefix.READ.value + lock_name
self.__write_lock_name = Prefix.WRITE.value + lock_name
self.__read_counter_key = Prefix.READ_COUNTER.value + lock_name
# self.__redis_host = redis_host
# self.__redis_port = redis_port
# self.__redis_key = redis_key
self.__cache = StrictRedis(host = redis_host,
port = redis_port,
db=0, # up to 16 logical database
password = redis_key,
ssl=True)
print(f'ping return:{self.__cache.ping()}')
# set the read counter to 0, if it does not exist.
self.__cache.setnx(self.__read_counter_key, 0)
# init the read lock
self.__read_lock = lock.Lock(self.__cache,
self.__read_lock_name,
RedisLockParams.TIMEOUT.value,
RedisLockParams.SLEEP_TIME.value,
RedisLockParams.BLOCKING.value,
RedisLockParams.BLOCKING_TIMEOUT.value)
# init the write lock
self.__write_lock = lock.Lock(self.__cache,
self.__write_lock_name,
RedisLockParams.TIMEOUT.value,
RedisLockParams.SLEEP_TIME.value,
RedisLockParams.BLOCKING.value,
RedisLockParams.BLOCKING_TIMEOUT.value)
def acquire_read_lock(self) -> bool:
write_lock_acquired = False
self.__read_lock.acquire()
read_counter = self.__cache.incr(self.__read_counter_key)
if (read_counter == 1):
write_lock_acquired = self.__write_lock.acquire() # acquire write lock
self.__read_lock.release()
return write_lock_acquired
def release_read_lock(self):
read_lock_acquired = self.__read_lock.acquire()
read_counter = self.__cache.decr(self.__read_counter_key)
if read_counter == 0 and read_lock_acquired:
self.__write_lock.release() # release the write lock-> issue!!!
self.__read_lock.release()
def acquire_write_lock(self) -> bool:
return self.__write_lock.acquire()
def release_write_lock(self):
self.__write_lock.release()

I am having the same issue.
"LockNotOwnedError("Cannot release a lock"\nredis.exceptions.LockNotOwnedError: Cannot release a lock that's no longer owned"
redis = Redis.from_url(redis_url)
try:
with redis.lock(name, timeout=timeout, blocking_timeout=blocking_timeout) as redis_lock:
yield redis_lock
except RedisLockError as e:
logger.warning("Cannot acquire lock", name=name, timeout=timeout, blocking_timeout=blocking_timeout)
raise LockError(f"Cannot acquire lock {name}") from e
Can someone add some hints?

I have figured out how to release the not-owned redis lock by taking a look at the redis' python library source code. Below is the modified version of the multiread-single-write lock class.
# read_write_lock.py
from enum import Enum
from redis import StrictRedis, lock
# data in Redis cache:
# "read_counter_lock_name" : 0
# "read_lock_lock_name" -> read lock, protect "read_counter_lock_name"
# "write_lock_lock_name" -> write lock, protect write data section
class Prefix(Enum):
READ = 'read_lock_'
WRITE = 'write_lock_'
READ_COUNTER = 'read_counter_'
class RedisLockParams(Enum):
TIMEOUT = 60 # maximum life for the lock in seconds = 60 seconds
SLEEP_TIME = 0.1 # the amount of time to sleep in seconds per loop iteration
# in redis lock's acquire() - sleep then retry
BLOCKING = True # acquire() should block until the lock has been acquired
BLOCKING_TIMEOUT = None # maximum amount of time in seconds to spend trying
# to acquire the lock
class ReadWriteLock:
def __init__(self, lock_name: str, redis_host: str, redis_port: int, redis_key: str):
self.__read_lock_name = Prefix.READ.value + lock_name
self.__write_lock_name = Prefix.WRITE.value + lock_name
self.__read_counter_key = Prefix.READ_COUNTER.value + lock_name
self.__cache = StrictRedis(host = redis_host,
port = redis_port,
db=0, # up to 16 logical database
password = redis_key,
ssl=True)
# set the read counter to 0, if it does not exist.
self.__cache.setnx(self.__read_counter_key, 0)
# init the read lock
self.__read_lock = lock.Lock(self.__cache,
self.__read_lock_name,
RedisLockParams.TIMEOUT.value,
RedisLockParams.SLEEP_TIME.value,
RedisLockParams.BLOCKING.value,
RedisLockParams.BLOCKING_TIMEOUT.value)
# init the write lock
self.__write_lock = lock.Lock(self.__cache,
self.__write_lock_name,
RedisLockParams.TIMEOUT.value,
RedisLockParams.SLEEP_TIME.value,
RedisLockParams.BLOCKING.value,
RedisLockParams.BLOCKING_TIMEOUT.value)
def acquire_read_lock(self) -> bool:
write_lock_acquired = False
self.__read_lock.acquire()
read_counter = self.__cache.incr(self.__read_counter_key)
if (read_counter == 1):
write_lock_acquired = self.__write_lock.acquire() # acquire write lock
self.__read_lock.release()
return write_lock_acquired
def release_read_lock(self):
read_lock_acquired = self.__read_lock.acquire()
if read_lock_acquired:
read_counter = self.__cache.decr(self.__read_counter_key)
if read_counter == 0:
if self.__write_lock.owned():
self.__write_lock.release()
else: # if the lock was not owned, just take its token and override
write_lock_token = self.__cache.get(self.__write_lock_name)
self.__write_lock.local.token = write_lock_token
self.__write_lock.release()
self.__read_lock.release()
def acquire_write_lock(self) -> bool:
return self.__write_lock.acquire()
def release_write_lock(self) -> None:
self.__write_lock.release()

Related

ForkingPickler: TypeError: cannot pickle 'memoryview' object

I am trying to send and receive pickled versions of a random value generated by the producer. I am using the Multiprocess(not '-ing') and ForkingPickler module to to pickle and qeueue the generated value. However upon running the sample program below, i get the below error. Basis for using ForkingPickler is to pickle socket objects in future. I am now testing out with a sample version. Is this a feasible way to go about pickling socket objects?
rv = reduce(self.proto)
TypeError: cannot pickle 'memoryview' object
def producer(queue):
print('Producer: Running', flush=True)
# generate work
for i in range(10):
# generate a value
value = random()
# block
sleep(value)
# add to the queue
fork_value = ForkingPickler.dumps(value)
queue.put(fork_value)
# all done
queue.put(None)
print(f'Queue Size Consumer: {queue.qsize()}', flush=True)
print('Producer: Done', flush=True)
# consume work
def consumer(queue):
print('Consumer: Running', flush=True)
# consume work
while True:
print(f'Queue Size Consumer: {queue.qsize()}', flush=True)
# get a unit of work
fork_value = queue.get()
item = ForkingPickler.loads(fork_value)
# check for stop
if item is None:
break
# report
print(f'>got {item}', flush=True)
# all done
print('Consumer: Done', flush=True)
# entry point
if __name__ == '__main__':
# create the shared queue
queue = JoinableQueue()
# start the consumer
consumer_process = Process(target=consumer, args=(queue,))
consumer_process.start()
# start the producer
producer_process = Process(target=producer, args=(queue,))
producer_process.start()
# wait for all processes to finish
consumer_process.join()
producer_process.join()

Remove trailing bits from hex pyModBus

I want to built a function that sends a request from ModBus to serial in hex. I more o less have a working function but have two issues.
Issue 1
[b'\x06', b'\x1c', b'\x00!', b'\r', b'\x1e', b'\x1d\xd3', b'\r', b'\n', b'\x1e', b'\x1d']
I cant remove this part b'\r', b'\n', using the .split('\r \n') method since It's not a string.
Issue 2
When getting a value from holding register 40 (33) and i try to use the .to_bytes() method I keep getting b'\x00!', b'\r' and I'm expecting b'\x21'
r = client.read_holding_registers(40)
re = r.registers[0]
req = re.to_bytes(2, 'big')
My functions to generate my request and to send trough pyserial.
def scanned_code():
code = client.read_holding_registers(0)
# code2= client.re
r = code.registers[0]
return r
def send_request(data):
""" Takes input from create_request() and sends data to serial port"""
try:
for i in range(data):
serial_client.write(data[i])
# serial_client.writelines(data[i])
except:
print('no se pudo enviar el paquete <<<--------------------')
def create_request(job):
""" Request type is 33 looks for job
[06]
[1c]
req=33[0d][0a]
job=30925[0d][0a][1e]
[1d]
"""
r = client.read_holding_registers(40)
re = r.registers[0]
req = re.to_bytes(2, 'big')
num = job.to_bytes(2, 'big')
data = [
b'\x06',
b'\x1C',
req,
b'\x0D',
b'\x1E',
num,
b'\x0D',
b'\x0A',
b'\x1E',
b'\x1D'
]
print(data)
while True:
# verify order_trigger() is True.
while order_trigger() != False:
print('inside while loop')
# set flag coil back to 0
reset_trigger()
# get Job no.
job = scanned_code()
# check for JOB No. dif. than 0
if job != 0:
print(scanned_code())
send_request(create_request(job))
# send job request to host to get job data
# send_request()
# if TRUE send job request by serial to DVI client
# get job request data
# translate job request data to modbus
# send data to plc
else:
print(' no scanned code')
break
time.sleep(INTERNAL_SLEEP_TIME)
print('outside loop')
time.sleep(EXTERNAL_SLEEP_TIME)
As an additional question is this the proper way of doing things?

Problems running Cygnus with Postgresql

So I have installed Cygnus and in the simple test configuration case which I took from here (https://github.com/telefonicaid/fiware-cygnus/blob/master/cygnus-ngsi/README.md) everything works fine.
But I need Postgresql as a backend for my application.
For this I adjusted the agent_1.conf file with all postgresql parameters found from http://fiware-cygnus.readthedocs.io/en/latest/cygnus-ngsi/installation_and_administration_guide/ngsi_agent_conf/
cygnus-ngsi.sources = http-source
cygnus-ngsi.sinks = postgresql-sink
cygnus-ngsi.channels = postgresql-channel
cygnus-ngsi.sources.http-source.channels = hdfs-channel mysql-channel ckan-channel mongo-channel sth-channel kafka-channel dynamo-channel postgresql-channel
cygnus-ngsi.sources.http-source.type = org.apache.flume.source.http.HTTPSource
cygnus-ngsi.sources.http-source.port = 5050
cygnus-ngsi.sources.http-source.handler = com.telefonica.iot.cygnus.handlers.NGSIRestHandler
cygnus-ngsi.sources.http-source.handler.notification_target = /notify
cygnus-ngsi.sources.http-source.handler.default_service = default
cygnus-ngsi.sources.http-source.handler.default_service_path = /
cygnus-ngsi.sources.http-source.interceptors = ts gi
cygnus-ngsi.sources.http-source.interceptors.gi.type = com.telefonica.iot.cygnus.interceptors.NGSIGroupingInterceptor$Builder
cygnus-ngsi.sources.http-source.interceptors.gi.grouping_rules_conf_file = /usr/cygnus/conf/grouping_rules.conf
cygnus-ngsi.sinks.postgresql-sink.channel = postgresql-channel
cygnus-ngsi.sinks.postgresql-sink.type = com.telefonica.iot.cygnus.sinks.NGSIPostgreSQLSink
cygnus-ngsi.sinks.postgresql-sink.postgresql_host = 127.0.0.1
cygnus-ngsi.sinks.postgresql-sink.postgresql_port = 5432
cygnus-ngsi.sinks.postgresql-sink.postgresql_database = myUser
cygnus-ngsi.sinks.postgresql-sink.postgresql_username = mydb
cygnus-ngsi.sinks.postgresql-sink.postgresql_password = xxxx
cygnus-ngsi.sinks.postgresql-sink.attr_persistence = row
cygnus-ngsi.sinks.postgresql-sink.batch_size = 100
cygnus-ngsi.sinks.postgresql-sink.batch_timeout = 30
cygnus-ngsi.sinks.postgresql-sink.batch_ttl = 10
# postgresql-channel configuration
cygnus-ngsi.channels.postgresql-channel.type = memory
cygnus-ngsi.channels.postgresql-channel.capacity = 1000
cygnus-ngsi.channels.postgresql-channel.transactionCapacity = 100
I didn'r really find any information about other files I am supposed to change and aren't really sure if all parameters are correct.
I also tried the sample configuration from here http://fiware-cygnus.readthedocs.io/en/latest/cygnus-ngsi/flume_extensions_catalogue/ngsi_postgresql_sink/index.html
Cygnus seems to start correctly but all if I try to send a notification I get connection refused
Befor doing anything, please, create the database, the user and the password in Postgresql.
The cygnus configuration is like this one. The /etc/cygnus/conf/cygnus_instance_1.conf file:
CYGNUS_USER=cygnus
CONFIG_FOLDER=/usr/cygnus/conf
CONFIG_FILE=/usr/cygnus/conf/agent_1.conf
AGENT_NAME=cygnus-ngsi
LOGFILE_NAME=cygnus.log
ADMIN_PORT=8081
POLLING_INTERVAL=30
So, the other file /usr/cygnus/conf/agent_1.conf is like this one (please change PostgreSQL parameters):
cygnus-ngsi.sources = http-source
cygnus-ngsi.sinks = postgresql-sink
cygnus-ngsi.channels = postgresql-channel
cygnus-ngsi.sources.http-source.channels = postgresql-channel
cygnus-ngsi.sources.http-source.type = org.apache.flume.source.http.HTTPSource
cygnus-ngsi.sources.http-source.port = 5050
cygnus-ngsi.sources.http-source.handler = com.telefonica.iot.cygnus.handlers.NGSIRestHandler
cygnus-ngsi.sources.http-source.handler.notification_target = /notify
cygnus-ngsi.sources.http-source.handler.default_service = default
cygnus-ngsi.sources.http-source.handler.default_service_path = /
cygnus-ngsi.sources.http-source.handler.events_ttl = 10
cygnus-ngsi.sources.http-source.interceptors = ts gi
cygnus-ngsi.sources.http-source.interceptors.ts.type = timestamp
cygnus-ngsi.sources.http-source.interceptors.gi.type = com.telefonica.iot.cygnus.interceptors.NGSIGroupingInterceptor$Builder
#cygnus-ngsi.sources.http-source.interceptors.gi.grouping_rules_conf_file = /usr/cygnus/conf/grouping_rules.conf
# =============================================
# postgresql-channel configuration
# channel type (must not be changed)
cygnus-ngsi.channels.postgresql-channel.type = memory
# capacity of the channel
cygnus-ngsi.channels.postgresql-channel.capacity = 1000
# amount of bytes that can be sent per transaction
cygnus-ngsi.channels.postgresql-channel.transactionCapacity = 100
# ============================================
# NGSIPostgreSQLSink configuration
# channel name from where to read notification events
cygnus-ngsi.sinks.postgresql-sink.channel = postgresql-channel
# sink class, must not be changed
cygnus-ngsi.sinks.postgresql-sink.type = com.telefonica.iot.cygnus.sinks.NGSIPostgreSQLSink
# true applies the new encoding, false applies the old encoding.
# cygnus-ngsi.sinks.postgresql-sink.enable_encoding = false
# true if the grouping feature is enabled for this sink, false otherwise
cygnus-ngsi.sinks.postgresql-sink.enable_grouping = false
# true if name mappings are enabled for this sink, false otherwise
cygnus-ngsi.sinks.postgresql-sink.enable_name_mappings = false
# true if lower case is wanted to forced in all the element names, false otherwise
# cygnus-ngsi.sinks.postgresql-sink.enable_lowercase = false
# the FQDN/IP address where the PostgreSQL server runs
cygnus-ngsi.sinks.postgresql-sink.postgresql_host = 127.0.0.1
# the port where the PostgreSQL server listens for incomming connections
cygnus-ngsi.sinks.postgresql-sink.postgresql_port = 5432
# the name of the postgresql database
cygnus-ngsi.sinks.postgresql-sink.postgresql_database = cygnusdb
# a valid user in the PostgreSQL server
cygnus-ngsi.sinks.postgresql-sink.postgresql_username = cygnus
# password for the user above
cygnus-ngsi.sinks.postgresql-sink.postgresql_password = cygnusdb
# how the attributes are stored, either per row either per column (row, column)
cygnus-ngsi.sinks.postgresql-sink.attr_persistence = row
# select the data_model: dm-by-service-path or dm-by-entity
cygnus-ngsi.sinks.postgresql-sink.data_model = dm-by-entity
# number of notifications to be included within a processing batch
cygnus-ngsi.sinks.postgresql-sink.batch_size = 1
# timeout for batch accumulation
cygnus-ngsi.sinks.postgresql-sink.batch_timeout = 30
# number of retries upon persistence error
cygnus-ngsi.sinks.postgresql-sink.batch_ttl = 0
# true enables cache, false disables cache
cygnus-ngsi.sinks.postgresql-sink.backend.enable_cache = true

Python 2.7 Tkinter is not response when run program

I'm absolute beginner for python Tkinter. My program has serial port and TCP client socket connection (Running in thread). It's running well in console application but not work in Tkinter GUI.
count = 0
initialState = True
def initState(reader, ReaderName, readerType, serialport, baud, databit, readerPacket):
global count
global initialState
if initialState:
while not reader.SettingReader(ReaderName, readerType, serialport, baud, databit, readerPacket):
count += 1
count = 0
labelSearching.place(x=290, y=260)
labelReaderSetting.configure(image=readerSettingSuccess)
app.update_idletasks()
labelSearching.grid_forget()
labelReaderConnect.place(x=290, y=260)
app.update_idletasks()
labelReaderConnect.configure(image=readerConnected)
labelServerConnect.place(x=290, y=320)
app.update_idletasks()
while not reader.StartServer():
count += 1
count = 0
labelServerConnect.configure(image=serverConnected)
app.update_idletasks()
labelContainer.grid_forget()
labelReaderSetting.configure(image=readerSettingSuccessSmall)
labelReaderSetting.place(x=80, y=200)
labelReaderSetting.lift()
labelReaderConnect.configure(image=readerConnectedSmall)
labelReaderConnect.place(x=80, y=260)
labelReaderConnect.lift()
labelServerConnect.configure(image=serverConnectedSmall)
labelServerConnect.place(x=80, y=320)
labelServerConnect.lift()
labelWaitingTap.place(x=460, y=260)
labelLeft.grid(row=1, column=0)
labelRight.grid(row=1, column=1)
app.update_idletasks()
reader.SaveSettingToFile()
initialState = False
else:
runnMainProgram(reader)
app.update()
app.after(1000, functools.partial(initState, reader, ReaderName, readerType, serialport, baud, databit, readerPacket))
def runnMainProgram(reader):
try:
check = reader.StartReader(reader._CARDANDPASSWORD)
app.update_idletasks()
if check == True:
print "Open the door"
check = ""
print "Ready..."
app.update_idletasks()
elif check == False:
print "Doesn't Open The Door"
check = ""
print "Ready..."
app.update_idletasks()
elif check == 2:
print "Reader disconnect"
print "Reconnecting to Reader"
reader.ClosePort()
while not reader.OpenPort():
count += 1
count = 0
check = ""
print "Ready..."
app.update_idletasks()
except KeyboardInterrupt:
exit()
app.after(10, functools.partial(runnMainProgram, reader))
app = Tk()
app.title("Access Control")
app.geometry('800x610+200+50')
app.protocol('WM_DELETE_WINDOW', closewindow)
updateGUIThread = threading.Thread(target=updateGUI)
app.minsize('800', '610')
app.maxsize('800', '610')
"I'm create Tkinter widget here."
reader = Readers()
settingList = list()
readerType = ""
readerPacket = ""
try:
for line in fileinput.FileInput("Setting.txt", mode='r'):
settingList.append(line)
if str(line).find("DF760MSB", 0, len(str(line))) >= 0:
readerType = reader._DF760MSB
elif str(line).find("DF760LSB", 0, len(str(line))) >= 0:
readerType = reader._DF760LSB
else:
readerType = reader._DF760MSB
if str(line).find("SINGLEPACKET", 0, len(str(line))) >= 0:
readerPacket = reader.SINGLEPACKET
elif str(line).find("MULTIPACKET", 0, len(str(line))) >= 0:
readerPacket = reader.MULTIPACKETS
else:
readerPacket = reader.SINGLEPACKET
ReaderName = str(settingList[0]).rstrip()
baud = int(settingList[1])
databit = int(settingList[2])
HOST = str(settingList[3]).rstrip()
PORT = int(settingList[4])
TIMEOUT = int(settingList[5])
except:
ReaderName = "R001"
baud = 19200
databit = 8
HOST = "10.50.41.81"
PORT = 43
TIMEOUT = 10
serialport = 'COM3'
reader.SettingServer(HOST, PORT, TIMEOUT)
app.after(100, functools.partial(initState, reader, ReaderName, readerType, serialport, baud, databit, readerPacket))
app.mainloop()
when I'm run this code GUI will freezing but serial port and TCP client socket still running.
I've try to fix this problem (looking in every where) but I'm got nothing. Any idea? Thank so much.
The way to solve this would be to call app.after(100, <methodName>) from the receiving thread. This stops the main thread from being blocked by waiting for a signal, but also means that tkinter can update instantly too as the method pushed to .after will be executed in the main thread. By specifying 100 as the time frame, it will appear to change nigh on instantly as the argument passed is the number of milliseconds.

PYSNMP stop and restart a trap reciever

I am trying to create a pysnmp daemon. I want to have the ability to start, stop, and restart the thread that the daemon is running on. I am having trouble cleaning the socket, notification receiver, and transport dispatcher.
I am using a pysnmp v1/2c trap receiver
class trapReceiverThread(threading.Thread):
def __init__(self):
try:
trapworking = snmpEngine.transportDispatcher.jobsArePending()
except:
trapworking = -1
if trapworking == 0:
snmpEngine.transportDispatcher.jobStarted(1)
elif trapworking == -1:
print "starting"
# UDP over IPv4, first listening interface/port
config.addV1System(snmpEngine, 'my-area', 'public')
# SecurityName <-> CommunityName mapping
print "d0"
config.addSocketTransport(
snmpEngine,
udp.domainName + (1,),
udp.UdpTransport().openServerMode(( 'localhost', 162 ))
)
ntfrcv.NotificationReceiver(snmpEngine, cbFun)
snmpEngine.transportDispatcher.jobStarted(1)
else:
print "Trap receiver already started."
def run(self):
try:
snmpEngine.transportDispatcher.runDispatcher()
except:
print "fail"
snmpEngine.transportDispatcher.closeDispatcher()
raise
def cbFun(snmpEngine,
stateReference,
contextEngineId, contextName,
varBinds,
cbCtx):
transportDomain, transportAddress = snmpEngine.msgAndPduDsp.getTransportInfo(stateReference)
print('Notification from %s, ContextEngineId "%s", ContextName "%s"' % (
transportAddress, contextEngineId.prettyPrint(),
contextName.prettyPrint()
)
)
for obj in varBinds:
print obj
trapStatus = threading.Thread(target = trapReceiverThread().run)
trapStatus.deamon = True
def start():
global trapStatus
if trapStatus.isAlive() == False:
try:
trapStatus.start();
except:
trapStatus = threading.Thread(target = trapReceiverThread().run)
trapStatus.start();
def stop():
if snmpEngine.transportDispatcher.jobsArePending():
print "stopping"
"""
CODE to stop SocketTransport, transportDispatcher, and NotificationReceiver
"""
snmpEngine.transportDispatcher.jobFinished(1)
trapStatus.join()
def restart():
stop()
start()
Since the trap is defined the the local variable transportDispater, the process can be stopped by finishing job #1 and releasing the port.
transportDispatcher.jobFinished(1)
transportDispatcher.unregisterRecvCbFun(recvId=None)
transportDispatcher.unregisterTransport(udp.domainName)