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.