Initializing pool connection variable with None.

Release pool connection handle und all circumstances especially also when a query fails
before the call is finished. This should avoid connection starvation.

fix prod. link

production fix

Fixed application path
This commit is contained in:
Daniel Schick 2025-11-12 13:55:58 +01:00
parent 63a3ce2f6f
commit b5dd7422f4
15 changed files with 101 additions and 77 deletions

View File

@ -69,7 +69,7 @@ def create_app(test_config=None, instance_path=None):
app.register_blueprint(history.bp)
app.register_blueprint(ports.bp)
logging.basicConfig(filename='brecaldevel.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
logging.basicConfig(filename='brecal.log', level=logging.WARNING, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool(os.path.dirname(app.instance_path))
logging.info('App started')

View File

@ -10,6 +10,7 @@ def GetBerths(options):
No parameters, gets all entries
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)

View File

@ -16,6 +16,8 @@ def GetHistory(options):
options["shipcall_id"]: **Id of shipcall**.
"""
pooledConnection = None
data = []
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -26,10 +28,6 @@ def GetHistory(options):
data = commands.query("SELECT id, participant_id, shipcall_id, timestamp, eta, type, operation FROM history WHERE shipcall_id = ?shipcallid?",
model=History.from_query_row,
param={"shipcallid" : options["shipcall_id"]})
pooledConnection.close()
except Exception as ex:
pdb.pm()
logging.error(ex)
@ -37,6 +35,9 @@ def GetHistory(options):
result = {}
result["error_field"] = "call failed"
return json.dumps("call failed"), 500
finally:
if pooledConnection is not None:
pooledConnection.close()
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -6,14 +6,15 @@ import bcrypt
from ..schemas import model
from .. import local_db
from ..services import jwt_handler
from BreCal.database.sql_queries import SQLQuery
def GetUser(options):
pooledConnection = None
try:
if "password" in options and "username" in options:
hash = bcrypt.hashpw(options["password"].encode('utf-8'), bcrypt.gensalt( 12 )).decode('utf8')
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_user()
@ -63,7 +64,3 @@ def GetUser(options):
finally:
if pooledConnection is not None:
pooledConnection.close()
# $2b$12$uWLE0r32IrtCV30WkMbVwOdltgeibymZyYAf4ZnQb2Bip8hrkGGwG
# $2b$12$.vEapj9xU8z0RK0IpIGeYuRIl0ktdMt4XdJQBhVn.3K2hmvm7qD3y
# $2b$12$yL3PiseU70ciwEuMVM4OtuMwR6tNuIT9vvBiBG/uyMrPxa16E2Zqu

View File

@ -11,13 +11,12 @@ def GetNotifications(token):
No parameters, gets all entries
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
data = commands.query("SELECT id, shipcall_id, participant_id, level, type, message, created, modified FROM notification " +
"WHERE level = 2", model=model.Notification.from_query_row)
pooledConnection.close()
except Exception as ex:
logging.error(ex)
@ -25,6 +24,9 @@ def GetNotifications(token):
result = {}
result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally:
if pooledConnection is not None:
pooledConnection.close()
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -12,6 +12,7 @@ def GetParticipant(options):
options["user_id"]: **Id of user**. *Example: 2*. User id returned by login call.
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)

View File

@ -11,6 +11,7 @@ def GetPorts(token):
No parameters, gets all entries
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)

View File

@ -18,8 +18,8 @@ def GetShipcalls(options):
No parameters, gets all entries
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_shipcalls(options)
@ -70,8 +70,8 @@ def PostShipcalls(schemaModel):
"""
# This creates a *new* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -192,8 +192,8 @@ def PutShipcalls(schemaModel):
"""
# This updates an *existing* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -205,7 +205,6 @@ def PutShipcalls(schemaModel):
theshipcall = commands.query_single_or_default("SELECT * FROM shipcall where id = ?id?", sentinel, param={"id" : schemaModel["id"]})
if theshipcall is sentinel:
pooledConnection.close()
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}
was_canceled = theshipcall["canceled"]

View File

@ -11,8 +11,8 @@ def GetShips(token):
No parameters, gets all entries
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_ships()
@ -44,8 +44,8 @@ def PostShip(schemaModel):
# TODO: Validate the incoming data
# This creates a *new* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -83,8 +83,6 @@ def PostShip(schemaModel):
# new_id = commands.execute_scalar(nquery)
new_id = commands.execute_scalar("select last_insert_id()")
pooledConnection.close()
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
@ -93,6 +91,9 @@ def PostShip(schemaModel):
result = {}
result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally:
if pooledConnection is not None:
pooledConnection.close()
def PutShip(schemaModel):
@ -101,8 +102,8 @@ def PutShip(schemaModel):
"""
# This updates an *existing* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -125,8 +126,6 @@ def PutShip(schemaModel):
affected_rows = commands.execute(query, param=schemaModel)
pooledConnection.close()
return json.dumps({"id" : schemaModel["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
@ -135,6 +134,9 @@ def PutShip(schemaModel):
result = {}
result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally:
if pooledConnection is not None:
pooledConnection.close()
def DeleteShip(options):
@ -143,16 +145,14 @@ def DeleteShip(options):
options["id"]
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_ship_delete_by_id()
# affected_rows = commands.execute(query, param={"id" : options["id"]})
affected_rows = commands.execute("UPDATE ship SET deleted = 1 WHERE id = ?id?", param={"id" : options["id"]})
pooledConnection.close()
if affected_rows == 1:
return json.dumps({"id" : options["id"]}), 200, {'Content-Type': 'application/json; charset=utf-8'}
@ -165,4 +165,7 @@ def DeleteShip(options):
print(ex)
result = {}
result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally:
if pooledConnection is not None:
pooledConnection.close()

View File

@ -18,8 +18,8 @@ def GetTimes(options):
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
# query = SQLQuery.get_times()
@ -28,7 +28,6 @@ def GetTimes(options):
"zone_entry, zone_entry_fixed, operations_start, operations_end, remarks, shipcall_id, participant_id, " +
"berth_id, berth_info, pier_side, participant_type, created, modified, ata, atd, eta_interval_end, etd_interval_end FROM times " +
"WHERE times.shipcall_id = ?scid?", model=model.Times, param={"scid" : options["shipcall_id"]})
pooledConnection.close()
except Exception as ex:
logging.error(traceback.format_exc())
@ -38,6 +37,10 @@ def GetTimes(options):
result["error_field"] = "call failed"
return json.dumps(result), 500, {'Content-Type': 'application/json; charset=utf-8'}
finally:
if pooledConnection is not None:
pooledConnection.close()
return json.dumps(data, default=model.obj_dict), 200, {'Content-Type': 'application/json; charset=utf-8'}
@ -51,8 +54,8 @@ def PostTimes(schemaModel):
# TODO: Validate the upload data
# This creates a *new* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -119,8 +122,8 @@ def PutTimes(schemaModel):
"""
# This updates an *existing* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -177,8 +180,8 @@ def DeleteTimes(options):
options["id"]
"""
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
shipcall_id = commands.execute_scalar("SELECT shipcall_id FROM times WHERE id = ?id?", param={"id" : options["id"]})
@ -207,4 +210,4 @@ def DeleteTimes(options):
finally:
if pooledConnection is not None:
pooledConnection.close()
pooledConnection.close()

View File

@ -14,8 +14,8 @@ def PutUser(schemaModel):
"""
# This updates an *existing* entry
pooledConnection = None
try:
pooledConnection = local_db.getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -26,7 +26,6 @@ def PutUser(schemaModel):
# theuser = commands.query_single_or_default(query, sentinel, param={"id" : schemaModel["id"]}, model=model.User)
theuser = commands.query_single_or_default("SELECT * FROM user where id = ?id?", sentinel, param={"id" : schemaModel["id"]}, model=model.User)
if theuser is sentinel:
pooledConnection.close()
# #TODO: result = {"message":"no such record"} -> json.dumps
return json.dumps("no such record"), 404, {'Content-Type': 'application/json; charset=utf-8'}

View File

@ -23,7 +23,7 @@ def _build_pool_config(connection_data, pool_name, pool_size):
return pool_config
def initPool(instancePath, connection_filename="connection_data_devel.json",
def initPool(instancePath, connection_filename="connection_data_prod.json",
pool_name="brecal_pool", pool_size=10):
"""
Initialize the MySQL connection pool and load email credentials.
@ -33,7 +33,7 @@ def initPool(instancePath, connection_filename="connection_data_devel.json",
if config_path is None:
config_path = os.path.join(instancePath, f'../../../secure/{connection_filename}')
config_path = 'C:\\temp\\connection_data_test.json'
# config_path = 'C:\\temp\\connection_data_test.json'
print(config_path)
if not os.path.exists(config_path):
@ -54,10 +54,10 @@ def initPool(instancePath, connection_filename="connection_data_devel.json",
finally:
conn_from_pool.close()
credentials_file = "email_credentials_devel.json"
credentials_file = "email_credentials_test.json"
credentials_path = os.path.join(instancePath, f'../../../secure/{credentials_file}')
credentials_path = 'C:\\temp\\email_credentials_test.json'
# credentials_path = 'C:\\temp\\email_credentials_test.json'
if not os.path.exists(credentials_path):
print('cannot find ' + os.path.abspath(credentials_path))

View File

@ -31,6 +31,7 @@ def UpdateShipcalls(options:dict = {'past_days':2}):
options:
key: 'past_days'. Is used to execute a filtered query of all available shipcalls. Defaults to 2 (days)
"""
pooledConnection = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -49,10 +50,11 @@ def UpdateShipcalls(options:dict = {'past_days':2}):
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=shipcall_id) # new_id (last insert id) refers to the shipcall id
pooledConnection.close()
except Exception as ex:
logging.error(ex)
finally:
if pooledConnection is not None:
pooledConnection.close()
return
def UpdateNotifications(cooldown_in_mins:int=10):
@ -61,6 +63,7 @@ def UpdateNotifications(cooldown_in_mins:int=10):
notification is updated to state 1 and a notification is received by the user
"""
pooledConnection = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -70,32 +73,39 @@ def UpdateNotifications(cooldown_in_mins:int=10):
for notification in data:
commands.execute("UPDATE notification SET level = 1 WHERE id = ?id?", param={"id":notification.id})
pooledConnection.close()
except Exception as ex:
logging.error(ex)
finally:
if pooledConnection is not None:
pooledConnection.close()
def ClearNotifications(max_age_in_days:int=3):
"""
This function clears all notifications in state ("level") 2 that are older than x days
"""
pooledConnection = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
query = f"DELETE FROM notification WHERE level = 2 and created < TIMESTAMP(NOW() - INTERVAL {max_age_in_days} DAY)"
result = commands.execute(query)
pooledConnection.close()
if(result > 0):
logging.info(f"Deleted {result} notifications")
except Exception as ex:
logging.error(ex)
finally:
if pooledConnection is not None:
pooledConnection.close()
def SendEmails(email_dict):
"""
This function sends emails to all users in the emaildict
"""
pooledConnection = None
conn = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
@ -177,10 +187,13 @@ def SendEmails(email_dict):
finally:
if conn is not None:
conn.quit()
if pooledConnection is not None:
pooledConnection.close()
def SendNotifications():
# perhaps this will be moved somewhere else later
pooledConnection = None
try:
# find all notifications in level 1
pooledConnection = getPoolConnection()
@ -271,6 +284,7 @@ def add_function_to_schedule_send_notifications(interval_in_minutes:int=1):
return
def eval_next_24_hrs():
pooledConnection = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)

View File

@ -90,40 +90,43 @@ class ValidationRules(ValidationRuleFunctions):
if evaluation_states_old is not None and evaluation_states_new is not None:
if len(evaluation_states_old) == 1 and len(evaluation_states_new) == 1:
if evaluation_states_old[0] != evaluation_states_new[0]:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
notification_type = 3 # RED (mapped to time_conflict)
if evaluation_states_new[0] == 2:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
notification_type = 6 # YELLOW (mapped to missing_data)
if evaluation_states_new[0] == 3:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
case 2:
send_notification = True
pooledConnection = None
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
notification_type = 3 # RED (mapped to time_conflict)
if evaluation_states_new[0] == 2:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
notification_type = 6 # YELLOW (mapped to missing_data)
if evaluation_states_new[0] == 3:
match evaluation_states_old[0]:
case 0:
send_notification = True
case 1:
send_notification = True
case 2:
send_notification = True
if send_notification:
query = f"INSERT INTO notification (shipcall_id, type, level, message) VALUES (?shipcall_id?, {notification_type}, 0, ?message?)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0]), "message" : violations[0]})
if send_notification:
query = f"INSERT INTO notification (shipcall_id, type, level, message) VALUES (?shipcall_id?, {notification_type}, 0, ?message?)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0]), "message" : violations[0]})
if evaluation_states_new[0] == 1 and evaluation_states_old[0] != 0: # this resolves the conflict
query = f"SELECT * from notification where shipcall_id = ?shipcall_id? and type = {notification_type} and level = 0"
existing_notification = commands.query(query, param={"shipcall_id" : int(shipcall_df.index[0])})
if len(existing_notification) > 0:
query = "DELETE from notification where id = ?id?"
commands.execute(query, param={"id" : existing_notification[0]["id"]})
else:
query = "INSERT INTO notification (shipcall_id, type, level) VALUES (?shipcall_id?, 4, 0)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0])})
pooledConnection.close()
if evaluation_states_new[0] == 1 and evaluation_states_old[0] != 0: # this resolves the conflict
query = f"SELECT * from notification where shipcall_id = ?shipcall_id? and type = {notification_type} and level = 0"
existing_notification = commands.query(query, param={"shipcall_id" : int(shipcall_df.index[0])})
if len(existing_notification) > 0:
query = "DELETE from notification where id = ?id?"
commands.execute(query, param={"id" : existing_notification[0]["id"]})
else:
query = "INSERT INTO notification (shipcall_id, type, level) VALUES (?shipcall_id?, 4, 0)"
commands.execute(query, param={"shipcall_id" : int(shipcall_df.index[0])})
finally:
if pooledConnection is not None:
pooledConnection.close()
# build the list of 'evaluation_notifications_sent'. The value is 'False', when a notification should be created

View File

@ -2,7 +2,7 @@ import os
import sys
import logging
sys.path.insert(0, '/var/www/brecal_devel/src/server')
sys.path.insert(0, '/var/www/brecal/src/server')
sys.path.insert(0, '/var/www/venv/lib/python3.12/site-packages/')
import schedule