What this is about
Rendering costs computing time. So that many simultaneous requests do not bring an application to a halt, CRMS renders in a bounded pool: a fixed number of threads with a short queue in front. On top of that come limits for time and size, and a sandbox: a closed environment in which the template runs and from which it reaches nothing.
Where each step runs
sequenceDiagram
participant C as Client
participant A as CRMS (caller's thread)
participant P as CRMS (render pool)
participant DB as Database
C->>A: render request
A->>DB: read caller, template, revision
A->>A: check format, mode, payload, attachments
A->>P: job into the pool
Note over P: run template, produce format, seal for FINAL
P-->>A: bytes (or error, timeout)
A->>DB: store for FINAL, in the caller's transaction
A-->>C: document
- On the caller’s thread: who is asking, language, template and revision, all checks. CRMS reads the database here, in the context of the request.
- In the pool: run the template and produce the format, under the timeout and the output limit. For
FINAL, the seal afterwards. - Back on the caller’s thread: a final document is stored before the bytes go back.
The limits
| Limit | Variable | Default | What happens beyond it |
|---|---|---|---|
| render threads | CODAMAI_CRMS_RENDER_THREADS | number of processors | – |
| queue | CODAMAI_CRMS_RENDER_QUEUE | 50 jobs | 503 render-capacity-exhausted, at once |
| timeout | CODAMAI_CRMS_RENDER_TIMEOUT | 30 s | the job is cancelled, 504 render-timeout |
| largest document | CODAMAI_CRMS_RENDER_MAX_OUTPUT | 50 MiB | 422 render-output-too-large |
| largest payload | CODAMAI_CRMS_PAYLOAD_MAX_BYTES | 5 MiB | 422 payload-rejected with payload-too-large |
| number of attachments | CODAMAI_CRMS_ATTACHMENTS_MAX_COUNT | 200 | 422 payload-rejected with attachments-too-many |
| size of all attachments | CODAMAI_CRMS_ATTACHMENTS_MAX_BYTES | 50 MiB | 422 payload-rejected with attachments-too-large |
Over REST, the whole body is also bounded before CRMS reads it: payload limit plus 64 KiB for the other fields plus the attachment limit in Base64 (four thirds) plus 512 bytes per allowed attachment. GET /crms/capabilities returns the current values under limits. How you set them is described in Environment variables.
The output limit applies twice: to the markup the template writes, and to the bytes the format produces. A loop that writes text endlessly therefore never fills the memory.
What can happen while running
| Room in the pool? | Done within the timeout? | Output within the limit? | Result |
|---|---|---|---|
| no | – | – | 503 render-capacity-exhausted: try again later |
| yes | no | – | 504 render-timeout: the job is interrupted |
| yes | yes | no | 422 render-output-too-large |
| yes | yes | yes | document |
If the caller itself is interrupted, for example because its request was cancelled, CRMS cancels the job too: 503 render-interrupted. A render never keeps running for nobody. An unexpected error in the pool is 500 render-failed. The details are then only in the log.
The template’s sandbox
Tenants and users write templates too. That is why FreeMarker, the template language, runs restricted:
| What | How it is in CRMS |
|---|---|
?new | creates no class at all |
?api | switched off. The template only sees text, numbers, booleans, dates, lists and maps and never reaches a method |
include, import | find nothing. A revision is exactly one source, without a file system |
| Escaping | automatic: XHTML for DOCUMENT, XML for TABLE and WORD |
| Markup from the payload | only through crms.svg, see Pictures and SVG |
| Endless loop | is interrupted at the timeout, because the sandbox checks for interruption inside loops |
What else the template language can do is described in The template language. An error in the template comes back as a key with line and column: template-syntax-error|<line>|<column> when reading, template-render-failed|<line>|<column> when running, for example for a missing value.
Which resources a format loads
A format loads nothing from the network and nothing from the file system. It gets everything in memory.
| Format | Allowed | Refused |
|---|---|---|
| HTML | asset:<name> becomes a data: address | an asset the revision does not have: render-asset-unknown |
asset:<name>, attachment:<name>, embedded fonts from CRMS | any other address, including http, https, file, jar, ftp, and unknown names: render-resource-refused | |
| DOCX | asset:<name> in <pkg:binaryData>, hyperlinks | any other external relationship: render-resource-refused. An unknown asset: render-asset-unknown. A DTD: render-word-invalid |
| XLSX, CSV, ODS | only the table document | a DTD or an unknown element: render-table-invalid |