Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions .github/workflows/frontend-pr-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ on:
description: 'Disable restore-keys to avoid restoring stale caches (forces exact key match)'
type: boolean
default: false
use-tmpfs:
description: 'Mount tmpfs at cache target paths (PLT-3439 test). RAM-backed, bypasses memcg dirty accounting.'
type: boolean
default: false

# Runner configuration
runner:
Expand Down Expand Up @@ -314,12 +318,13 @@ jobs:
uses: actions/checkout@v6

- name: Setup Node with Cache
uses: Typeform/.github/shared-actions/setup-node-with-cache@v1
uses: Typeform/.github/shared-actions/setup-node-with-cache@test-tmpfs-cache-extract
with:
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Setup Jarvis
Expand Down Expand Up @@ -387,12 +392,13 @@ jobs:
uses: actions/checkout@v6

- name: Setup Node with Cache
uses: Typeform/.github/shared-actions/setup-node-with-cache@v1
uses: Typeform/.github/shared-actions/setup-node-with-cache@test-tmpfs-cache-extract
with:
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Setup Jarvis
Expand Down Expand Up @@ -435,12 +441,13 @@ jobs:
uses: actions/checkout@v6

- name: Setup Node with Cache
uses: Typeform/.github/shared-actions/setup-node-with-cache@v1
uses: Typeform/.github/shared-actions/setup-node-with-cache@test-tmpfs-cache-extract
with:
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Setup Jarvis
Expand Down Expand Up @@ -501,12 +508,13 @@ jobs:
uses: actions/checkout@v6

- name: Setup Node with Cache
uses: Typeform/.github/shared-actions/setup-node-with-cache@v1
uses: Typeform/.github/shared-actions/setup-node-with-cache@test-tmpfs-cache-extract
with:
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Download Build Artifacts
Expand Down Expand Up @@ -574,13 +582,14 @@ jobs:
needs: [build, unit-tests]
permissions:
contents: read
uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@v1
uses: Typeform/.github/.github/workflows/sonarcloud-scan.yml@test-tmpfs-cache-extract
with:
app-name: ${{ inputs.app-name }}
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
runner: ${{ inputs.runner }}
coverage-artifact-name: ${{ inputs.run-unit-tests && format('coverage-{0}', github.run_id) || '' }}
timeout: ${{ inputs.sonarcloud-timeout }}
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/sonarcloud-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ on:
description: 'Disable restore-keys to avoid restoring stale caches (forces exact key match)'
type: boolean
default: false

use-tmpfs:
description: 'Mount tmpfs at cache target paths (PLT-3439 test).'
type: boolean
default: false

# Runner configuration
runner:
description: 'Runner for SonarCloud scan'
Expand Down Expand Up @@ -68,12 +72,13 @@ jobs:
fetch-depth: 0 # Required for SonarCloud to analyze git history

- name: Setup Node with Cache
uses: Typeform/.github/shared-actions/setup-node-with-cache@v1
uses: Typeform/.github/shared-actions/setup-node-with-cache@test-tmpfs-cache-extract
with:
node-version: ${{ inputs.node-version }}
use-asdf: ${{ inputs.use-asdf }}
cache-mode: ${{ inputs.cache-mode }}
disable-restore-keys: ${{ inputs.disable-restore-keys }}
use-tmpfs: ${{ inputs.use-tmpfs }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}

- name: Download coverage artifacts
Expand Down
50 changes: 50 additions & 0 deletions shared-actions/setup-node-with-cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,60 @@ inputs:
description: 'Disable restore-keys to avoid restoring stale caches (forces exact key match)'
required: false
default: 'false'
use-tmpfs:
description: 'Mount tmpfs (RAM-backed) at cache target paths to bypass disk I/O and memcg dirty-page accounting (PLT-3439).'
required: false
default: 'false'

runs:
using: 'composite'
steps:
# Mount tmpfs at paths that will receive cached content, before any cache
# restore runs. On ARC scale-set runners the pod's ephemeral storage is
# shared across the k8s node and is IOPS-throttled at the cgroup level;
# extracting hundreds of MB of small files into it triggers writeback
# backpressure that blocks subsequent syscalls for tens of seconds.
# Using tmpfs avoids both the disk and the memcg dirty-page budget.
# Graceful: if mount fails (e.g. no CAP_SYS_ADMIN), we log and continue
# with disk-backed paths. See https://typeform.atlassian.net/browse/PLT-3439.
- name: Mount tmpfs at cache targets
if: ${{ inputs.use-tmpfs == 'true' && !env.ACT }}
shell: bash
run: |
set +e
mount_tmpfs() {
local path="$1"
local size="$2"
sudo mkdir -p "$path" 2>/dev/null
if sudo mount -t tmpfs -o size="$size" tmpfs "$path" 2>/tmp/mount.err; then
sudo chown -R "$(id -u):$(id -g)" "$path" 2>/dev/null
echo "✓ mounted tmpfs size=$size at $path"
else
echo "⚠ tmpfs mount failed at $path: $(cat /tmp/mount.err 2>/dev/null)"
echo " Continuing with disk-backed storage."
fi
}

if [ "${{ inputs.use-asdf }}" == "true" ]; then
mount_tmpfs "$HOME/.asdf/installs" "2g"
mount_tmpfs "$HOME/.asdf/plugins" "200m"
fi

case "${{ inputs.cache-mode }}" in
full|node_modules-only|"")
mount_tmpfs "$GITHUB_WORKSPACE/node_modules" "3g"
;;
esac

case "${{ inputs.cache-mode }}" in
full|yarn-cache-only)
mount_tmpfs "$HOME/.cache/yarn" "1g"
;;
esac

echo "=== Mounted filesystems ==="
mount | grep -E 'tmpfs.*(asdf|node_modules|yarn)' || echo "(no tmpfs mounts present)"

# Cache 1: asdf installations (Node.js and Yarn binaries)
# This cache is separate because it only changes when .tool-versions is updated,
# which happens infrequently (only when upgrading Node/Yarn versions).
Expand Down