I found out the compiler is triggered twice in most browser causing race issues during compilation resulting int folder and file creation and deletion errors.
Steps to reproduce the Bug
compile any component in any browser and wait for compilation errors. this has been a longstanding issue and the fix was always to use firefox. But as of lately firefox also began behaving badly.
Which Joomla version are you compiling in?
6.1.3
Which PHP version are you compiling in?
8.4
Which Joomla versions are you targeting?
6
Which PHP version are you targeting?
8.4
Which Web server is JCB running on?
litespeed
Which Relational Database is JCB running on?
mariadb 11.x
Which OS is JCB running on?
almalinux 10
Which JCB version are you using?
6.1.6 stable
Where in JCB did this issue occur?
Compilation of a component
On which browsers did you encounter the issue?
Firefox, Chrome, Safari
Additional Comments
PR: compile button submits the form twice, causing two concurrent compiles
Compiling a component intermittently ends on the Joomla error page with one of:
Joomla\Filesystem\Folder::create: Could not create directory.
Path: [ROOT]/administrator/components/com_componentbuilder/compiler/<component>/…
Joomla\Filesystem\Folder::delete: Could not delete folder.
Path: [ROOT]/administrator/components/com_componentbuilder/compiler/<component>/…
The path is different every time. The compile itself completes — refreshing the
page shows the success screen and the component can be installed or downloaded.
It usually takes a few compiles in a row before it happens.
The button declares no type, so it defaults to type="submit", and it sits
inside the <form> opened on line 223. The inline handler does not cancel the
default action either (no return false). One click therefore fires both:
onclick -> Joomla.submitbutton() which sets form.task.value, hides #form, and schedules setTimeout(() => form.submit(), 100)
the browser's own native submit of the same form
Both requests carry task=compiler.compiler, because the onclick runs before the
default action. Two compilers then run against the same build directory.
Line 219 of the same file already gets this right with type="button".
Evidence
Logging one line per Compiler\Utilities\Folder::remove() call:
The directory already exists and already holds files written by the other
process. So: process A checks is_dir() -> false, process B creates the folder
and writes into it, process A calls mkdir() -> EEXIST.
The Could not delete folder variant is the same race in Initializer::resetBuildDirectory(), where one process removes a subtree while
the other is iterating it.
Confirmed independently by running two compiles over one build directory in
isolation: both messages reproduce, at varying paths.
Verification
With the one-line fix below applied, 10 consecutive compiles of the same
component: no failures, and no second PID ever touching the build directory
within the same second.
For reference, line 219 of the same file already uses type="button".
2. Suggested — make the compiler safe against concurrency
administrator/components/com_componentbuilder/src/Controller/CompilerController.php, compiler() starts at line 110. It checks the token (line 113) and core.manage (line 117), then calls $model->compile() at line 123 with no
guard of any kind:
Even with the button fixed, a resubmitted POST (refreshing while a compile runs)
or a second browser tab still collides. An exclusive lock held across $model->compile(), refusing or queueing a second run, removes the whole class
of failure rather than one trigger for it.
3. Suggested — stop discarding the OS error
libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Compiler/Utilities/Folder.php, create() at line 69, the call at lines 75-77:
publicfunctioncreate(string$path,bool$addHtml=true)// 69
{// check if the path exist
if(!is_dir($path))// 72
{// create the path
JoomlaFolder::create(// 75
$path);
Joomla\Filesystem\Folder::create() uses @mkdir(), so every report of this
bug says only "Could not create directory" with no reason. error_get_last()
still holds it — including it in the rethrown message would have made this
diagnosable immediately instead of requiring a patched file to find.
The same spot is also a check-then-act race. Tolerating it makes the compiler
resilient regardless of trigger:
if(!is_dir($path)){try{JoomlaFolder::create($path);}catch(\Throwable$e){clearstatcache(true,$path);// another process created it between the check and the call
if(!is_dir($path)){throw$e;}return;}
### What Happened?
I found out the compiler is triggered twice in most browser causing race issues during compilation resulting int folder and file creation and deletion errors.
### Steps to reproduce the Bug
compile any component in any browser and wait for compilation errors. this has been a longstanding issue and the fix was always to use firefox. But as of lately firefox also began behaving badly.
### Which Joomla version are you compiling in?
6.1.3
### Which PHP version are you compiling in?
8.4
### Which Joomla versions are you targeting?
6
### Which PHP version are you targeting?
8.4
### Which Web server is JCB running on?
litespeed
### Which Relational Database is JCB running on?
mariadb 11.x
### Which OS is JCB running on?
almalinux 10
### Which JCB version are you using?
6.1.6 stable
### Where in JCB did this issue occur?
Compilation of a component
### On which browsers did you encounter the issue?
Firefox, Chrome, Safari
### Additional Comments
# PR: compile button submits the form twice, causing two concurrent compiles
Repo: https://git.vdm.dev/joomla/Component-Builder
Found on JCB 6.1.6, Joomla 6.1.3, PHP 8.3.
## Symptom
Compiling a component intermittently ends on the Joomla error page with one of:
```
Joomla\Filesystem\Folder::create: Could not create directory.
Path: [ROOT]/administrator/components/com_componentbuilder/compiler/<component>/…
Joomla\Filesystem\Folder::delete: Could not delete folder.
Path: [ROOT]/administrator/components/com_componentbuilder/compiler/<component>/…
```
The path is different every time. The compile itself completes — refreshing the
page shows the success screen and the component can be installed or downloaded.
It usually takes a few compiles in a row before it happens.
## Cause
`administrator/components/com_componentbuilder/tmpl/compiler/default.php:235`
```html
<button class="btn btn-primary btn-lg px-4 me-sm-3" style="width: 100%;"
onclick="Joomla.submitbutton('compiler.compiler')">
```
The button declares no `type`, so it defaults to `type="submit"`, and it sits
inside the `<form>` opened on line 223. The inline handler does not cancel the
default action either (no `return false`). One click therefore fires both:
1. `onclick` -> `Joomla.submitbutton()` which sets `form.task.value`, hides
`#form`, and schedules `setTimeout(() => form.submit(), 100)`
2. the browser's own native submit of the same form
Both requests carry `task=compiler.compiler`, because the onclick runs before the
default action. Two compilers then run against the same build directory.
Line 219 of the same file already gets this right with `type="button"`.
## Evidence
Logging one line per `Compiler\Utilities\Folder::remove()` call:
```
22:00:52.1296 pid=123016 remove(.../compiler/com_component_v1_1_29__J6)
22:00:52.1316 pid=124403 remove(.../compiler/com_component_v1_1_29__J6)
```
Two PIDs, 2 ms apart, resetting the same build directory. Too fast to be a
double-click.
Joomla's `Folder::create()` calls `@mkdir()`, so the real reason is discarded.
Capturing `error_get_last()` at the throw site gives it:
```
path : .../com_component_v1_1_29__J6/libraries/tlweb_component/Component.Joomla/src/Data
mkdir said: mkdir(): File exists
ancestors : exists dir perms=0755 .../Component.Joomla/src/Data
listing : .../src/Data/index.html
.../src/Data/Factory.php
```
The directory already exists and already holds files written by the other
process. So: process A checks `is_dir()` -> false, process B creates the folder
and writes into it, process A calls `mkdir()` -> `EEXIST`.
The `Could not delete folder` variant is the same race in
`Initializer::resetBuildDirectory()`, where one process removes a subtree while
the other is iterating it.
Confirmed independently by running two compiles over one build directory in
isolation: both messages reproduce, at varying paths.
## Verification
With the one-line fix below applied, 10 consecutive compiles of the same
component: no failures, and no second PID ever touching the build directory
within the same second.
```
22:06:44 .. 22:07:45 pid=253633 (6 compiles)
22:08:32 .. 22:09:03 pid=284468 (4 compiles)
```
Two PIDs appear, but sequentially - the pool recycled the worker during the ~47s
gap. Before the fix the two PIDs hit the same path 2 ms apart.
## Fix
Paths below are from an installed JCB 6.1.6; adjust for the repo layout if it
differs.
| # | File | Line | Change |
|---|------|------|--------|
| 1 | `administrator/components/com_componentbuilder/tmpl/compiler/default.php` | 235 | add `type="button"` |
| 2 | `administrator/components/com_componentbuilder/src/Controller/CompilerController.php` | 110-127 | guard `compiler()` against concurrent runs |
| 3 | `libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Compiler/Utilities/Folder.php` | 69-80 | surface the OS error / tolerate the race |
### 1. The trigger — one attribute (this alone fixes the reported bug)
`administrator/components/com_componentbuilder/tmpl/compiler/default.php` line 235:
```diff
--- a/administrator/components/com_componentbuilder/tmpl/compiler/default.php
+++ b/administrator/components/com_componentbuilder/tmpl/compiler/default.php
@@ -235,1 +235,1 @@
- <button class="btn btn-primary btn-lg px-4 me-sm-3" style="width: 100%;" onclick="Joomla.submitbutton('compiler.compiler')"><span class="icon-cog icon-white"></span>
+ <button type="button" class="btn btn-primary btn-lg px-4 me-sm-3" style="width: 100%;" onclick="Joomla.submitbutton('compiler.compiler')"><span class="icon-cog icon-white"></span>
```
For reference, line 219 of the same file already uses `type="button"`.
### 2. Suggested — make the compiler safe against concurrency
`administrator/components/com_componentbuilder/src/Controller/CompilerController.php`,
`compiler()` starts at line 110. It checks the token (line 113) and
`core.manage` (line 117), then calls `$model->compile()` at line 123 with no
guard of any kind:
```php
public function compiler(): bool
{
Session::checkToken() or \jexit(Text::_('JINVALID_TOKEN')); // 113
$user = $this->app->getIdentity(); // 116
if (!$user->authorise('core.manage', 'com_componentbuilder')) // 117
{
return false;
}
$model = $this->getModel('Compiler');
if (!$model->compile()) // 123
{
return false;
}
```
Even with the button fixed, a resubmitted POST (refreshing while a compile runs)
or a second browser tab still collides. An exclusive lock held across
`$model->compile()`, refusing or queueing a second run, removes the whole class
of failure rather than one trigger for it.
### 3. Suggested — stop discarding the OS error
`libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Compiler/Utilities/Folder.php`,
`create()` at line 69, the call at lines 75-77:
```php
public function create(string $path, bool $addHtml = true) // 69
{
// check if the path exist
if (!is_dir($path)) // 72
{
// create the path
JoomlaFolder::create( // 75
$path
);
```
`Joomla\Filesystem\Folder::create()` uses `@mkdir()`, so every report of this
bug says only "Could not create directory" with no reason. `error_get_last()`
still holds it — including it in the rethrown message would have made this
diagnosable immediately instead of requiring a patched file to find.
The same spot is also a check-then-act race. Tolerating it makes the compiler
resilient regardless of trigger:
```php
if (!is_dir($path))
{
try
{
JoomlaFolder::create($path);
}
catch (\Throwable $e)
{
clearstatcache(true, $path);
// another process created it between the check and the call
if (!is_dir($path))
{
throw $e;
}
return;
}
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What Happened?
I found out the compiler is triggered twice in most browser causing race issues during compilation resulting int folder and file creation and deletion errors.
Steps to reproduce the Bug
compile any component in any browser and wait for compilation errors. this has been a longstanding issue and the fix was always to use firefox. But as of lately firefox also began behaving badly.
Which Joomla version are you compiling in?
6.1.3
Which PHP version are you compiling in?
8.4
Which Joomla versions are you targeting?
6
Which PHP version are you targeting?
8.4
Which Web server is JCB running on?
litespeed
Which Relational Database is JCB running on?
mariadb 11.x
Which OS is JCB running on?
almalinux 10
Which JCB version are you using?
6.1.6 stable
Where in JCB did this issue occur?
Compilation of a component
On which browsers did you encounter the issue?
Firefox, Chrome, Safari
Additional Comments
PR: compile button submits the form twice, causing two concurrent compiles
Repo: https://git.vdm.dev/joomla/Component-Builder
Found on JCB 6.1.6, Joomla 6.1.3, PHP 8.3.
Symptom
Compiling a component intermittently ends on the Joomla error page with one of:
The path is different every time. The compile itself completes — refreshing the
page shows the success screen and the component can be installed or downloaded.
It usually takes a few compiles in a row before it happens.
Cause
administrator/components/com_componentbuilder/tmpl/compiler/default.php:235The button declares no
type, so it defaults totype="submit", and it sitsinside the
<form>opened on line 223. The inline handler does not cancel thedefault action either (no
return false). One click therefore fires both:onclick->Joomla.submitbutton()which setsform.task.value, hides#form, and schedulessetTimeout(() => form.submit(), 100)Both requests carry
task=compiler.compiler, because the onclick runs before thedefault action. Two compilers then run against the same build directory.
Line 219 of the same file already gets this right with
type="button".Evidence
Logging one line per
Compiler\Utilities\Folder::remove()call:Two PIDs, 2 ms apart, resetting the same build directory. Too fast to be a
double-click.
Joomla's
Folder::create()calls@mkdir(), so the real reason is discarded.Capturing
error_get_last()at the throw site gives it:The directory already exists and already holds files written by the other
process. So: process A checks
is_dir()-> false, process B creates the folderand writes into it, process A calls
mkdir()->EEXIST.The
Could not delete foldervariant is the same race inInitializer::resetBuildDirectory(), where one process removes a subtree whilethe other is iterating it.
Confirmed independently by running two compiles over one build directory in
isolation: both messages reproduce, at varying paths.
Verification
With the one-line fix below applied, 10 consecutive compiles of the same
component: no failures, and no second PID ever touching the build directory
within the same second.
Two PIDs appear, but sequentially - the pool recycled the worker during the ~47s
gap. Before the fix the two PIDs hit the same path 2 ms apart.
Fix
Paths below are from an installed JCB 6.1.6; adjust for the repo layout if it
differs.
administrator/components/com_componentbuilder/tmpl/compiler/default.phptype="button"administrator/components/com_componentbuilder/src/Controller/CompilerController.phpcompiler()against concurrent runslibraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Compiler/Utilities/Folder.php1. The trigger — one attribute (this alone fixes the reported bug)
administrator/components/com_componentbuilder/tmpl/compiler/default.phpline 235:For reference, line 219 of the same file already uses
type="button".2. Suggested — make the compiler safe against concurrency
administrator/components/com_componentbuilder/src/Controller/CompilerController.php,compiler()starts at line 110. It checks the token (line 113) andcore.manage(line 117), then calls$model->compile()at line 123 with noguard of any kind:
Even with the button fixed, a resubmitted POST (refreshing while a compile runs)
or a second browser tab still collides. An exclusive lock held across
$model->compile(), refusing or queueing a second run, removes the whole classof failure rather than one trigger for it.
3. Suggested — stop discarding the OS error
libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Compiler/Utilities/Folder.php,create()at line 69, the call at lines 75-77:Joomla\Filesystem\Folder::create()uses@mkdir(), so every report of thisbug says only "Could not create directory" with no reason.
error_get_last()still holds it — including it in the rethrown message would have made this
diagnosable immediately instead of requiring a patched file to find.
The same spot is also a check-then-act race. Tolerating it makes the compiler
resilient regardless of trigger:
I can confirm that this is an issue with JCB 4.1.5 as well, and just started breaking on Librewolf also.
This should be fixed now, please confirm.