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.

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)

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.