Is it possible with Crystal to start a server and Webview from the same executable

I tried the following, but it doesn’t seem to be the right way to do it.

However, if I split the app into a server and a client parts (2 separate executables) and start them separately, then it works.

However, if I do it as below, the server seems to stop instead of running in its own process (fiber)

require "webview"
require "http"
require "file_utils"

cwd = FileUtils.pwd()
content = Path.new(cwd, "axino/dist/")
path = content.to_s
pp path

spawn do
  server = HTTP::Server.new([
    HTTP::LogHandler.new,
    HTTP::ErrorHandler.new,
    HTTP::StaticFileHandler.new(path),
  ])
  address = server.bind_tcp 8081
  puts "Listening on http://#{address} and serving files in path #{path}"
  server.listen
end

spawn do 
  wv = Webview.window(640, 480, Webview::SizeHints::NONE, "Hello WebView", "http://localhost:8081/index.html", true)
  wv.bind("f", Webview::JSProc.new { 
    msg = %({"m": "message from Webview"})
    pp ("My message !")
    JSON::Any.new(msg)
  })
  wv.run
  wv.destroy
end
2 Likes

I think the webview#run is the culprit. The server.listen allows other fibers to run in the thread while the connections are waited using IO events. But webview#run does not yield the fiber execution.

If you want this you need to go to multi-thread or at least as an alternative wraps the wv.run in a

Thread.new do
  wv.run # probably wv.not_nil!.run
end

but note that manually creating Threads is not recommended / encouraged, and it might break depending on how the library internally works.

1 Like

For completeness, here is the corresponding code in Go which works:

package main

import (
	"github.com/webview/webview"
)

func main() {

	go server()

	const (
		debug = true
	)

	w := webview.New(debug)
	defer w.Destroy()
	w.SetTitle("Minimal webview example")
	w.SetSize(800, 600, webview.HintNone)
	//w.Navigate("https://en.m.wikipedia.org/wiki/Main_Page")
	w.Navigate("http://127.0.0.1:8081")
	w.Run()
}

One could think that even if manually creating Threads will have problems (with doing IO, sleeping, Channel interaction etc), it would work if mt_preview was specified. Unfortunately however, the current scheduler will portion out newly spawned fibers over all the available threads, so if anything blocks without yielding to the scheduler it will block any fiber that is assigned to the same thread until it is done.

So a quick fix would consist in starting the two executables in sequence from a script or from a third file (using system calls), I guess.

Go routines are spawned in multiple threads which are created by go runtime and that’s the default feature of Go. Crystal default is single-threaded, where it spawns fibers in same thread. So to achieve your goals you will have to either go with mt_preview mode for multi-threaded feature or the work-around of running two processes (as you mentioned in your comment).

2 Likes

Using Process.fork seems appropriate here.

3 Likes

Somebody should fix wv.run if that’s possible… :slight_smile: If you run with GOMAXPROCS=1 does it also block? If not why not I wonder…

1 Like

The app built in Go also works with :

runtime.GOMAXPROCS(1)

Yes, because when Go does a C call it does it in a new thread.

2 Likes

Even with mt_preview on this may or may not work depending if the Fiber used to do the webview.run call end up in the same thread of webserver or not. The solution is to manually create a thread as bcardiff said or modify Crystal compiler to have an annotation on C function to tell it must call them in another thread like asterite said and Go does

Yes, but spawn default settings (argument same_thread is set to false as default value) is to run in separate thread. So for cited or in question use-case mt_preview with more than 1 thread is definitely going to work, as question was to run webserver and webview in same process.

same_thread: false doesn’t force the fiber to be scheduled on the same thread. But it does not exclude that. It’s up to the scheduler where it gets dispatched.

i.e. just same_thread: true has a deterministic behavior.

1 Like

As it seems to fit well to this topic:
It’s likely not meant to be used, I guess, but why: Thread.new ?

It seems to work fine (but as it’s not mentioned anywhere, I guess one really isn’t supposed to use it).