wailsapp/wails · critical

failed to init GTK

Error message

failed to init GTK

What it means

CoCreateInstance panics with 'E_OUTOFMEMORY' in the w32 wrapper when the COM activation could not allocate memory. For out-of-process servers this can mean the server process could not be started due to resource limits; for in-process servers it is straightforward allocation failure. As with other COM memory failures, the arguments were acceptable and the system was simply out of resources.

Solutions

  1. Reduce memory pressure before activation: drop caches, free large buffers.
  2. Throttle concurrent CoCreateInstance calls with a semaphore instead of unbounded parallelism.
  3. For local-server classes, verify the server executable can launch (permissions, desktop heap) — check Event Viewer for DCOM errors.
  4. Recover() and retry once after debug.FreeOSMemory() if transient pressure is expected.

Example fix

// before
for _, item := range items {
	go func(){ CoCreateInstance(&clsid, CLSCTX_ALL, &riid, ppv) }() // unbounded -> OOM panic
}

// after
sem := make(chan struct{}, 4)
for _, item := range items {
	sem <- struct{}{}
	go func(){ defer func(){ <-sem }(); CoCreateInstance(&clsid, CLSCTX_ALL, &riid, ppv) }()
}
Defensive patterns

Strategy: retry

Try / catch

func createCOMObject(clsid, riid *syscall.GUID, ppv uintptr) (err error) {
	for attempt := 0; attempt < 3; attempt++ {
		ok := func() bool {
			defer func() {
				if r := recover(); r != nil {
					if msg, _ := r.(string); strings.Contains(msg, "E_OUTOFMEMORY") {
						return
					}
					panic(r)
				}
			}()
			CoCreateInstance(clsid, w32.CLSCTX_ALL, riid, ppv)
			return true
		}()
		if ok {
			return nil
		}
		debug.FreeOSMemory()
		time.Sleep(time.Duration(attempt+1) * 50 * time.Millisecond)
	}
	return errors.New("com activation failed after retries")
}

Prevention

When it happens

Trigger: Activating a COM class while the process is at its commit limit; launching a LocalServer COM executable (e.g. an Office or shell component) on a machine with exhausted desktop heap or process slots; large numbers of simultaneous COM activations from worker goroutines.

Common situations: Batch operations that instantiate shell COM objects (thumbnails, notifications) in parallel on memory-tight machines; CI runners with low memory ceilings where local testing passed but the runner panics.

Related errors


AI-assisted analysis of wailsapp/wails@0e754b1b40 (2026-08-15). Data as JSON: /api/errors/48ded1e541694097. Report an issue: GitHub.

Appendix: source

Thrown at v2/internal/frontend/desktop/linux/frontend.go:182

func (f *Frontend) RunMainLoop() {
	C.gtk_main()
}

func (f *Frontend) WindowClose() {
	f.mainWindow.Destroy()
}

func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
	initOnce.Do(func() {
		runtime.LockOSThread()

		// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
		if os.Getenv("GDK_BACKEND") == "" && (os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") {
			_ = os.Setenv("GDK_BACKEND", "x11")
		}

		if ok := C.gtk_init_check(nil, nil); ok != 1 {
			panic(errors.New("failed to init GTK"))
		}
	})

	result := &Frontend{
		frontendOptions: appoptions,
		logger:          myLogger,
		bindings:        appBindings,
		dispatcher:      dispatcher,
		ctx:             ctx,
	}
	result.startURL, _ = url.Parse(startURL)
	result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)

	if _starturl, _ := ctx.Value("starturl").(*url.URL); _starturl != nil {
		result.startURL = _starturl
		result.originValidator = originvalidator.NewOriginValidator(result.startURL, appoptions.BindingsAllowedOrigins)
	} else {
		if port, _ := ctx.Value("assetserverport").(string); port != "" {

View on GitHub (pinned to 0e754b1b40)