So I went live with my embedded Base-X website and that has been a very painful learning experience.
I had been opening the DB on each request and running various queries, but assumed that the process would close it when it exited. That was bad, bad, bad. About an hour after I uploaded the new site (and after I'd gone to bed for a nine hour snooze), the site started serving a 500 error code because too many files were open.
OK, figured that out. So I created a singleton and with a method that opens the database in a try/catch block, and closes it in the finally block. Problem solved.
Sort of.
Apologies for the Scala, but here is the singleton code:
object DB { private val context = new Context() new org.basex.core.cmd.Set("dbpath", "/var/www/db").execute(context)
def executeQuery(query: String): String = { try { new Open("data").execute(context) new XQuery(query).execute(context) } catch { case _ => new CreateDB("data", "<data/>").execute(context) new XQuery(query).execute(context) } finally { new Close().execute(context) } } }
Try blocks in Scala return a value, so this method returns the output of the passed-in XQuery. Works like a charm.
When a request is received, my servlet (using Circumflex, a Scala web framework) creates a new RequestRouter class. A "get" method is called and passed the URL. Here is that class and method, which shows how I am calling the above DB object:
class Main extends RequestRouter { get(url) = { val query = Rx.getQuery(url) val output = DB.executeQuery(qry)
"<!DOCTYPE html>\n" + output } }
This passes the URL (url) to a method on another singleton (Rx.getQuery) which returns an XQuery based on the URL. That query is passed to DB.executeQuery which stores the output in the immutable variable "output." I then prepend a doctype and serve it as text/html.
This runs great... for about an hour. Then the server stops responding altogether. I've searched the logs, and I found this error:
java.io.IOException: Stream Closed java.io.RandomAccessFile.seek(Native Method) org.basex.io.TableDiskAccess.readBlock(TableDiskAccess.java:349) org.basex.io.TableDiskAccess.cursor(TableDiskAccess.java:326) org.basex.io.TableDiskAccess.read1(TableDiskAccess.java:92) org.basex.data.Data.kind(Data.java:305) org.basex.query.item.DBNode$4.next(DBNode.java:273) org.basex.query.path.IterStep$1.next(IterStep.java:45) org.basex.query.path.AxisPath.iter(AxisPath.java:437) org.basex.query.path.AxisPath.iter(AxisPath.java:406) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.For$1.init(For.java:127) org.basex.query.expr.For$1.next(For.java:92) org.basex.query.expr.FLWR$1.next(FLWR.java:63) org.basex.query.expr.Constr.<init>(Constr.java:53) org.basex.query.expr.CElem.item(CElem.java:112) org.basex.query.expr.CElem.item(CElem.java:1) org.basex.query.expr.CFrag.item(CFrag.java:1) org.basex.query.expr.ParseExpr.iter(ParseExpr.java:49) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.FLWR$1.next(FLWR.java:67) org.basex.query.iter.Iter.finish(Iter.java:65) org.basex.query.expr.ParseExpr.value(ParseExpr.java:73) org.basex.query.expr.Func.iter(Func.java:80) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.FuncCall.iter(FuncCall.java:76) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.TypeCase.iter(TypeCase.java:95) org.basex.query.expr.TypeSwitch.iter(TypeSwitch.java:74) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.Constr.<init>(Constr.java:52) org.basex.query.up.Replace.item(Replace.java:50) org.basex.query.expr.ParseExpr.iter(ParseExpr.java:49) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.ParseExpr.value(ParseExpr.java:73) org.basex.query.expr.GFLWOR.iter(GFLWOR.java:298) org.basex.query.expr.GFLWOR.iter(GFLWOR.java:291) org.basex.query.expr.GFLWOR.iter(GFLWOR.java:266) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.ParseExpr.value(ParseExpr.java:73) org.basex.query.up.Transform.iter(Transform.java:86) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.FLWR$1.next(FLWR.java:67) org.basex.query.iter.Iter.finish(Iter.java:65) org.basex.query.expr.ParseExpr.value(ParseExpr.java:73) org.basex.query.expr.Func.iter(Func.java:80) org.basex.query.QueryContext.iter(QueryContext.java:306) org.basex.query.expr.FuncCall.iter(FuncCall.java:76) org.basex.query.QueryContext.iter(QueryContext.java:306)
I'm guessing that this is some sort of race condition?
What is the trick to using Base-X in embedded mode? Do I need to be doing some sort of threading here? (I have managed to avoid learning anything about threads for 15 years now. Hate to ruin that record.)
Suggestions? Advice? At this point I'm seriously thinking of just running Base-X as a server and connecting through a connection pool, but I have to figure that out tonight, so if there's an easier answer, I'd sure love to know it.
Thanks again for all your help!
Chas.