diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 846e84377e14c66e5be41ad9a0d208e7100a2d27..7541c8ac2bb2b35476830dd45d163b6c4098da3b 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -5,6 +5,7 @@ */ import { Structure, StructureElement } from '../structure'; +import { now } from 'mol-task'; export interface QueryContextView { readonly element: StructureElement; @@ -14,6 +15,8 @@ export interface QueryContextView { export class QueryContext implements QueryContextView { private currentElementStack: StructureElement[] = []; private currentStructureStack: Structure[] = []; + private timeCreated = now(); + private timeoutMs: number; readonly inputStructure: Structure; @@ -40,8 +43,18 @@ export class QueryContext implements QueryContextView { else (this.currentStructure as Structure) = void 0 as any; } - constructor(structure: Structure) { + throwIfTimedOut() { + if (this.timeoutMs === 0) return; + if (now() - this.timeCreated > this.timeoutMs) { + throw new Error(`The query took too long to execute (> ${this.timeoutMs / 1000}s).`); + } + } + + // todo timeout + + constructor(structure: Structure, timeoutMs = 0) { this.inputStructure = structure; + this.timeoutMs = timeoutMs; } } diff --git a/src/mol-model/structure/query/queries/combinators.ts b/src/mol-model/structure/query/queries/combinators.ts index 7bcf70406da68f04a13562fa50006c616588464b..854c54726292a58380805bf3578da5e8fb8414fe 100644 --- a/src/mol-model/structure/query/queries/combinators.ts +++ b/src/mol-model/structure/query/queries/combinators.ts @@ -11,8 +11,9 @@ export function merge(queries: ArrayLike<StructureQuery>): StructureQuery { return ctx => { const ret = StructureSelection.UniqueBuilder(ctx.inputStructure); for (let i = 0; i < queries.length; i++) { - StructureSelection.forEach(queries[i](ctx), s => { + StructureSelection.forEach(queries[i](ctx), (s, j) => { ret.add(s); + if (i % 100) ctx.throwIfTimedOut(); }); } return ret.getSelection(); diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index 6d7363123cf450d8cf0be06e276fdb86504601d2..49ca5dc201f31af2bb1cf9575f7bde6453b10313 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -16,9 +16,10 @@ export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuer const sel = query(ctx); const ret = StructureSelection.LinearBuilder(ctx.inputStructure); ctx.pushCurrentElement(); - StructureSelection.forEach(sel, s => { + StructureSelection.forEach(sel, (s, i) => { ctx.currentStructure = s; if (pred(ctx)) ret.add(s); + if (i % 100) ctx.throwIfTimedOut(); }); ctx.popCurrentStructure(); return ret.getSelection(); @@ -44,6 +45,8 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType l.element = elements[j]; set.add(fn(ctx)); } + + ctx.throwIfTimedOut(); } ctx.popCurrentElement(); return set; @@ -54,9 +57,11 @@ function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: const sel = query(ctx); ctx.pushCurrentElement(); - StructureSelection.forEach(sel, s => { + StructureSelection.forEach(sel, (s, i) => { ctx.currentStructure = s; getCurrentStructureProperties(ctx, props, set); + + if (i % 10) ctx.throwIfTimedOut(); }); ctx.popCurrentElement(); return set; @@ -69,12 +74,14 @@ export function withSameAtomProperties(query: StructureQuery, propertySource: St const ret = StructureSelection.LinearBuilder(ctx.inputStructure); ctx.pushCurrentStructure(); - StructureSelection.forEach(sel, s => { + StructureSelection.forEach(sel, (s, i) => { ctx.currentStructure = s; const currentProps = getCurrentStructureProperties(ctx, props, new Set()); if (isSuperset(currentProps, propSet)) { ret.add(s); } + + if (i % 10) ctx.throwIfTimedOut(); }); ctx.popCurrentStructure(); return ret.getSelection(); @@ -86,8 +93,9 @@ export function areIntersectedBy(query: StructureQuery, by: StructureQuery): Str const mask = StructureSelection.unionStructure(by(ctx)); const ret = StructureSelection.LinearBuilder(ctx.inputStructure); - StructureSelection.forEach(query(ctx), s => { + StructureSelection.forEach(query(ctx), (s, i) => { if (structureAreIntersecting(mask, s)) ret.add(s); + if (i % 10) ctx.throwIfTimedOut(); }); return ret.getSelection(); }; diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index ef9da1a1863010b8935be8d901ca28c9c74be8d3..c7e7d770e41dd544afeb30d9485fb92383c5d56a 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -60,6 +60,8 @@ function atomGroupsLinear(atomTest: QueryPredicate): StructureQuery { if (atomTest(ctx)) builder.addElement(l.element); } builder.commitUnit(); + + ctx.throwIfTimedOut(); } ctx.popCurrentElement(); return StructureSelection.Singletons(inputStructure, builder.getStructure()); @@ -105,6 +107,8 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A } } builder.commitUnit(); + + ctx.throwIfTimedOut(); } ctx.popCurrentElement(); return StructureSelection.Singletons(inputStructure, builder.getStructure()); @@ -146,6 +150,8 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group } } } + + ctx.throwIfTimedOut(); } ctx.popCurrentElement(); return builder.getSelection(); diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts index ba3404e3482e13d001030543a4a47b99131ed122..f6add3bbec78f401ac461fec32b2ef36767e429b 100644 --- a/src/mol-model/structure/query/queries/modifiers.ts +++ b/src/mol-model/structure/query/queries/modifiers.ts @@ -10,8 +10,9 @@ import { StructureQuery } from '../query'; import { StructureSelection } from '../selection'; import { UniqueStructuresBuilder } from '../utils/builders'; import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder'; +import { QueryContext } from '../context'; -function getWholeResidues(source: Structure, structure: Structure) { +function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) { const builder = source.subsetBuilder(true); for (const unit of structure.units) { if (unit.kind !== Unit.Kind.Atomic) { @@ -32,6 +33,8 @@ function getWholeResidues(source: Structure, structure: Structure) { } } builder.commitUnit(); + + ctx.throwIfTimedOut(); } return builder.getStructure(); } @@ -40,11 +43,11 @@ export function wholeResidues(query: StructureQuery, isFlat: boolean): Structure return ctx => { const inner = query(ctx); if (StructureSelection.isSingleton(inner)) { - return StructureSelection.Singletons(ctx.inputStructure, getWholeResidues(ctx.inputStructure, inner.structure)); + return StructureSelection.Singletons(ctx.inputStructure, getWholeResidues(ctx, ctx.inputStructure, inner.structure)); } else { const builder = new UniqueStructuresBuilder(ctx.inputStructure); for (const s of inner.structures) { - builder.add(getWholeResidues(ctx.inputStructure, s)); + builder.add(getWholeResidues(ctx, ctx.inputStructure, s)); } return builder.getSelection(); } @@ -60,7 +63,7 @@ export interface IncludeSurroundingsParams { wholeResidues?: boolean } -function getIncludeSurroundings(source: Structure, structure: Structure, params: IncludeSurroundingsParams) { +function getIncludeSurroundings(ctx: QueryContext, source: Structure, structure: Structure, params: IncludeSurroundingsParams) { const builder = new StructureUniqueSubsetBuilder(source); const lookup = source.lookup3d; const r = params.radius; @@ -72,21 +75,23 @@ function getIncludeSurroundings(source: Structure, structure: Structure, params: const e = elements[i]; lookup.findIntoBuilder(x(e), y(e), z(e), r, builder); } + + ctx.throwIfTimedOut(); } - return !!params.wholeResidues ? getWholeResidues(source, builder.getStructure()) : builder.getStructure(); + return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure(); } export function includeSurroundings(query: StructureQuery, params: IncludeSurroundingsParams): StructureQuery { return ctx => { const inner = query(ctx); if (StructureSelection.isSingleton(inner)) { - const surr = getIncludeSurroundings(ctx.inputStructure, inner.structure, params); + const surr = getIncludeSurroundings(ctx, ctx.inputStructure, inner.structure, params); const ret = StructureSelection.Singletons(ctx.inputStructure, surr); return ret; } else { const builder = new UniqueStructuresBuilder(ctx.inputStructure); for (const s of inner.structures) { - builder.add(getIncludeSurroundings(ctx.inputStructure, s, params)); + builder.add(getIncludeSurroundings(ctx, ctx.inputStructure, s, params)); } return builder.getSelection(); } diff --git a/src/mol-model/structure/query/query.ts b/src/mol-model/structure/query/query.ts index 98f8ab4d71b559f1fe9acc4d390f618a8daf1d1d..4283b6b73029bf88d6c793253fb6871f6657ed60 100644 --- a/src/mol-model/structure/query/query.ts +++ b/src/mol-model/structure/query/query.ts @@ -10,8 +10,8 @@ import { QueryContext } from './context'; interface StructureQuery { (ctx: QueryContext): StructureSelection } namespace StructureQuery { - export function run(query: StructureQuery, structure: Structure) { - return query(new QueryContext(structure)) + export function run(query: StructureQuery, structure: Structure, timeoutMs = 0) { + return query(new QueryContext(structure, timeoutMs)); } } diff --git a/src/servers/model/server/query.ts b/src/servers/model/server/query.ts index 908f16dc833fc56c814b2691d3df213265f9c093..13a1816935f010f35d9627c23a576133f631fe05 100644 --- a/src/servers/model/server/query.ts +++ b/src/servers/model/server/query.ts @@ -32,11 +32,12 @@ export async function resolveJob(job: Job, writer: Writer) { const wrappedStructure = await getStructure(job); perf.start('query'); + // TODO: encode errors that happen past this point as CIF rather than just 404 const structure = job.queryDefinition.structureTransform ? await job.queryDefinition.structureTransform(job.normalizedParams, wrappedStructure.structure) : wrappedStructure.structure; const query = job.queryDefinition.query(job.normalizedParams, structure); - const result = StructureSelection.unionStructure(StructureQuery.run(query, structure)); + const result = StructureSelection.unionStructure(StructureQuery.run(query, structure, Config.maxQueryTimeInMs)); perf.end('query'); ConsoleLogger.logId(job.id, 'Query', 'Query finished.');