Compare commits
5 Commits
0ee55ee73d
...
refactor-e
| Author | SHA1 | Date | |
|---|---|---|---|
| 06f81652b7 | |||
| ed6d1dd40d | |||
| 7b1a7ea30d | |||
| 5b0bfb3619 | |||
| 74647bcdfb |
4
BUGs
4
BUGs
@@ -1,5 +1,5 @@
|
|||||||
### Next: 146
|
### Next: 147
|
||||||
BUG-140: When db is restarted underneath PA, it crashes job mgr... It should just accept timeouts, and keep trying to reconnect every 2? mins
|
BUG-146: with an empty DB, I See 'No files in Path!' twice (for file*, except for files_rbp)
|
||||||
BUG-118: can move files from Bin path, but it leaves the del_file entry for it - need to remove it
|
BUG-118: can move files from Bin path, but it leaves the del_file entry for it - need to remove it
|
||||||
BUG-117: when search returns files that can be deleted and/or restored, the icon stays as delete and tries to delete!
|
BUG-117: when search returns files that can be deleted and/or restored, the icon stays as delete and tries to delete!
|
||||||
BUG-106: cant add trudy /pat? as refimgs via FaceDBox
|
BUG-106: cant add trudy /pat? as refimgs via FaceDBox
|
||||||
|
|||||||
11
main.py
11
main.py
@@ -66,12 +66,19 @@ app.config['LDAP_USER_DN'] = 'ou=users'
|
|||||||
app.config['LDAP_GROUP_DN'] = 'ou=groups'
|
app.config['LDAP_GROUP_DN'] = 'ou=groups'
|
||||||
app.config['LDAP_USER_RDN_ATTR'] = 'uid'
|
app.config['LDAP_USER_RDN_ATTR'] = 'uid'
|
||||||
app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
|
app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
|
||||||
app.config['LDAP_BIND_USER_DN'] = None
|
|
||||||
app.config['LDAP_BIND_USER_PASSWORD'] = None
|
|
||||||
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)'
|
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)'
|
||||||
app.config['LDAP_BIND_USER_DN'] = None
|
app.config['LDAP_BIND_USER_DN'] = None
|
||||||
app.config['LDAP_BIND_USER_PASSWORD'] = None
|
app.config['LDAP_BIND_USER_PASSWORD'] = None
|
||||||
|
|
||||||
|
# stop db restarts from causing stales and client-side 'server errors' - its a
|
||||||
|
# touch hacky, e.g. it issues a select 1 before EVERY request, likely should
|
||||||
|
# ditch this and just have a short-lived pool, but need to work out if/where I
|
||||||
|
# can catch the right exception myself and then dont need this, but for now...
|
||||||
|
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
||||||
|
"pool_pre_ping": True,
|
||||||
|
"pool_recycle": 280, # Good practice to include this with pre-ping
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
db = SQLAlchemy(app) # create the (flask) sqlalchemy connection
|
db = SQLAlchemy(app) # create the (flask) sqlalchemy connection
|
||||||
ma = Marshmallow(app) # set up Marshmallow - data marshalling / serialising
|
ma = Marshmallow(app) # set up Marshmallow - data marshalling / serialising
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from sqlalchemy.orm import relationship
|
|||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from sqlalchemy.orm import scoped_session
|
from sqlalchemy.orm import scoped_session
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
### LOCAL FILE IMPORTS ###
|
### LOCAL FILE IMPORTS ###
|
||||||
from shared import DB_URL, PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, THUMBSIZE, SymlinkName, GenThumb, SECS_IN_A_DAY, PA_EXIF_ROTATER, PA
|
from shared import DB_URL, PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT, THUMBSIZE, SymlinkName, GenThumb, SECS_IN_A_DAY, PA_EXIF_ROTATER, PA
|
||||||
@@ -66,21 +67,41 @@ override_tbls={ "face_no_match_override", "face_force_match_override", "disconne
|
|||||||
# this is required to handle the duplicate processing code
|
# this is required to handle the duplicate processing code
|
||||||
sys.setrecursionlimit(50000)
|
sys.setrecursionlimit(50000)
|
||||||
|
|
||||||
# a Manager, which the Session will use for connection resources
|
|
||||||
some_engine = create_engine(DB_URL)
|
|
||||||
|
|
||||||
# create a configured "Session" class
|
# 1. Add pool_pre_ping and pool_recycle here to handle db container disappearing underneath us
|
||||||
#Session = sessionmaker(bind=some_engine)
|
some_engine = create_engine(
|
||||||
|
DB_URL,
|
||||||
|
pool_pre_ping=True, # check DB connection is still active before use
|
||||||
|
pool_recycle=300, # churn connections regardless every 5 mins
|
||||||
|
pool_size=20, # Parallel-ready base pool
|
||||||
|
max_overflow=10 # Burst capacity for high socket traffic
|
||||||
|
)
|
||||||
|
|
||||||
# create a Session
|
|
||||||
session_factory = sessionmaker(bind=some_engine)
|
session_factory = sessionmaker(bind=some_engine)
|
||||||
Session = scoped_session(session_factory)
|
Session = scoped_session(session_factory)
|
||||||
session = Session()
|
|
||||||
|
# HACK: need to remove this and use 'sess' as an actual param everywhere, butt here are 200+ so quick fix until retired
|
||||||
|
session = Session
|
||||||
|
|
||||||
|
# this is a way to handle a session failing
|
||||||
|
@contextmanager
|
||||||
|
def PA_db_session():
|
||||||
|
"""Provide a transactional scope around a series of operations."""
|
||||||
|
# This creates a NEW session from the registry
|
||||||
|
s = Session()
|
||||||
|
try:
|
||||||
|
yield s
|
||||||
|
s.commit()
|
||||||
|
except Exception:
|
||||||
|
s.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# This destroys the session and returns connection to pool
|
||||||
|
Session.remove()
|
||||||
|
|
||||||
# this creates the Base (like db model in flask)
|
# this creates the Base (like db model in flask)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Class describing PathType & in the database (via sqlalchemy)
|
# Class describing PathType & in the database (via sqlalchemy)
|
||||||
# series of pre-defined types of paths (import, storage, bin)
|
# series of pre-defined types of paths (import, storage, bin)
|
||||||
@@ -2751,7 +2772,13 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
InitialValidationChecks()
|
InitialValidationChecks()
|
||||||
|
|
||||||
HandleJobs(True)
|
# Initial job run on startup (hence True in 1st param)
|
||||||
|
try:
|
||||||
|
with PA_db_session() as sess:
|
||||||
|
HandleJobs(True)
|
||||||
|
except Exception as e:
|
||||||
|
PAprint(f"ERROR: Initial job handle failed: {e}")
|
||||||
|
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
s.bind((PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT))
|
s.bind((PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT))
|
||||||
# force timeout every 1 day so we can run scheduled jobs
|
# force timeout every 1 day so we can run scheduled jobs
|
||||||
@@ -2759,18 +2786,35 @@ if __name__ == "__main__":
|
|||||||
s.listen()
|
s.listen()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
# 1. Wait for connection
|
||||||
conn, addr = s.accept()
|
conn, addr = s.accept()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
PAprint( f"accept finished, tout={s.timeout}" )
|
PAprint(f"Connection accepted from {addr}")
|
||||||
|
|
||||||
|
# 2. Process Jobs after a successful socket connection
|
||||||
|
with PA_db_session() as sess:
|
||||||
|
HandleJobs(False)
|
||||||
|
# Check for scheduled tasks as well
|
||||||
|
if ScheduledJobs():
|
||||||
|
HandleJobs(False)
|
||||||
|
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
PAprint( f"timeout occurred, tout={s.timeout}" )
|
PAprint("Socket timeout (Daily maintenance window) reached.")
|
||||||
if ScheduledJobs():
|
|
||||||
HandleJobs(False)
|
# 3. Process Scheduled Jobs during the timeout
|
||||||
|
try:
|
||||||
|
with PA_db_session() as sess:
|
||||||
|
if ScheduledJobs():
|
||||||
|
HandleJobs(False)
|
||||||
|
except sqlalchemy.exc.OperationalError:
|
||||||
|
PAprint("DB Connection lost during scheduled task window. Retrying next cycle.")
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
HandleJobs(False)
|
except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.InterfaceError) as e:
|
||||||
# in case we constantly have jobs running, the '1 day' last import might be missed, so check it after each job too
|
# This catches the DB container restart specifically
|
||||||
if ScheduledJobs():
|
PAprint(f"DATABASE ERROR: Connection lost. Retrying... {e}")
|
||||||
HandleJobs(False)
|
time.sleep(5) # Brief pause before next socket listen
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
PAprint(f"UNEXPECTED ERROR: {e}")
|
||||||
|
|||||||
Reference in New Issue
Block a user