MHSanaei/3x-ui · warning
xray is already running
Error message
xray is already running
What it means
process.Start() refuses to launch a second xray-core child from the same process object when IsRunning() is already true. The check reads the PID/exit state under the hood, so this fires on double-start races or reusing a process instance that is still alive. It protects against two xray binaries fighting over the same config/API port.
Source
Thrown at internal/xray/process.go:518
func (p *process) refreshVersion() {
version := "Unknown"
ctx, cancel := context.WithTimeout(context.Background(), xrayVersionTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, GetBinaryPath(), "-version")
if data, err := cmd.Output(); err == nil {
if datas := bytes.Split(data, []byte(" ")); len(datas) > 1 {
version = string(datas[1])
}
}
p.mu.Lock()
p.version = version
p.mu.Unlock()
}
// Start launches the Xray process with the current configuration.
func (p *process) Start() (err error) {
if p.IsRunning() {
return errors.New("xray is already running")
}
defer func() {
if err != nil {
logger.Error("Failure in running xray-core process: ", err)
p.setExitErr(err)
}
}()
data, err := json.MarshalIndent(p.config, "", " ")
if err != nil {
return common.NewErrorf("Failed to generate XRAY configuration files: %v", err)
}
err = os.MkdirAll(config.GetLogFolder(), 0o770)
if err != nil {
logger.Warningf("Failed to create log folder: %s", err)
}View on GitHub (pinned to ad32144c42)
Solutions
- Check IsRunning() before calling Start, or serialize starts behind the existing service-level lock (RestartXray already holds it).
- If you truly want a fresh core, call Stop (and wait) before Start — i.e. use RestartXray instead of Start.
- Audit for duplicate start paths (startup task + manual API) firing at the same time.
Example fix
// before
err := p.Start() // "xray is already running"
// after
if p.IsRunning() {
err = p.Stop()
if err != nil { return err }
}
err = p.Start() Defensive patterns
Strategy: validation
Validate before calling
if p.IsRunning() {
// already started; nothing to do or use Restart instead
return nil
}
return p.Start() Try / catch
if err := p.Start(); err != nil && strings.Contains(err.Error(), "already running") {
return nil // idempotent start
} else if err != nil {
return err
} Prevention
- Always start xray through XrayService (RestartXray path) so the package lock serializes starts.
- Never call Start concurrently from scripts and panel at boot.
When it happens
Trigger: Two concurrent RestartXray calls, or Start called from both the startup task and an API handler; re-Start after a hot-reload attempt that did not go through Stop; racing the crash-restart handler.
Common situations: Concurrent admin operations triggering restarts; custom scripts invoking the start path while the panel already started xray; test code that starts the same process object twice.
Related errors
- traffic writer stopped before write completed
- xray is not running
- xray is not running
- token not found
- local xray is not running
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/1997cf3b4d0f1d63.
Report an issue: GitHub.