Skip to content

Commit

Permalink
improved error reporting through the crash dialog. now both Exit and …
Browse files Browse the repository at this point in the history
…Crash are terminal states
  • Loading branch information
Yatao Li committed Dec 6, 2019
1 parent 30d61fa commit 83a7798
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 109 deletions.
188 changes: 97 additions & 91 deletions Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,104 +22,110 @@ open MessagePack.Formatters
// Avalonia configuration, don't remove; also used by visual designer.
[<CompiledName "BuildAvaloniaApp">]
let buildAvaloniaApp() =
AppBuilder
.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.With(new Win32PlatformOptions(UseDeferredRendering=false, AllowEglInitialization=true))
.With(new AvaloniaNativePlatformOptions(UseDeferredRendering=false, UseGpu=true))
.With(new X11PlatformOptions(UseEGL=true, UseGpu=false))
.With(new MacOSPlatformOptions(ShowInDock=true))
.LogToDebug()
AppBuilder
.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.With(new Win32PlatformOptions(UseDeferredRendering=false, AllowEglInitialization=true))
.With(new AvaloniaNativePlatformOptions(UseDeferredRendering=false, UseGpu=true))
.With(new X11PlatformOptions(UseEGL=true, UseGpu=false))
.With(new MacOSPlatformOptions(ShowInDock=true))
.LogToDebug()

type MsgPackFormatter(resolver: IFormatterResolver) =
let m_formatter = resolver.GetFormatter<obj>()
interface IMessagePackFormatter<obj> with
member this.Serialize(bytes: byref<byte []>, offset: int, value: obj, formatterResolver: IFormatterResolver): int =
m_formatter.Serialize(&bytes, offset, value, formatterResolver)
member x.Deserialize(bytes: byte[] , offset: int, formatterResolver: IFormatterResolver , readSize: byref<int>) =
if MessagePackBinary.GetMessagePackType(bytes, offset) = MessagePackType.Extension then
let result = MessagePackBinary.ReadExtensionFormat(bytes, offset, &readSize)
if result.TypeCode = 1y then
let mutable _size = 0
m_formatter.Deserialize(result.Data, 0, formatterResolver, &_size)
else
m_formatter.Deserialize(bytes, offset, formatterResolver, &readSize)
else
m_formatter.Deserialize(bytes, offset, formatterResolver, &readSize)
let m_formatter = resolver.GetFormatter<obj>()
interface IMessagePackFormatter<obj> with
member this.Serialize(bytes: byref<byte []>, offset: int, value: obj, formatterResolver: IFormatterResolver): int =
m_formatter.Serialize(&bytes, offset, value, formatterResolver)
member x.Deserialize(bytes: byte[] , offset: int, formatterResolver: IFormatterResolver , readSize: byref<int>) =
if MessagePackBinary.GetMessagePackType(bytes, offset) = MessagePackType.Extension then
let result = MessagePackBinary.ReadExtensionFormat(bytes, offset, &readSize)
if result.TypeCode = 1y then
let mutable _size = 0
m_formatter.Deserialize(result.Data, 0, formatterResolver, &_size)
else
m_formatter.Deserialize(bytes, offset, formatterResolver, &readSize)
else
m_formatter.Deserialize(bytes, offset, formatterResolver, &readSize)

type MsgPackResolver() =
static let s_formatter = box(MsgPackFormatter(MessagePack.Resolvers.StandardResolver.Instance))
static let s_resolver = MessagePack.Resolvers.StandardResolver.Instance
interface IFormatterResolver with
member x.GetFormatter<'a>() =
if typeof<'a> = typeof<obj> then
s_formatter :?> IMessagePackFormatter<'a>
else
s_resolver.GetFormatter<'a>()
static let s_formatter = box(MsgPackFormatter(MessagePack.Resolvers.StandardResolver.Instance))
static let s_resolver = MessagePack.Resolvers.StandardResolver.Instance
interface IFormatterResolver with
member x.GetFormatter<'a>() =
if typeof<'a> = typeof<obj> then
s_formatter :?> IMessagePackFormatter<'a>
else
s_resolver.GetFormatter<'a>()


[<EntryPoint>]
[<CompiledName "Main">]
let main(args: string[]) =

let _ = Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA)

CompositeResolver.RegisterAndSetAsDefault(
MsgPackResolver()
// ImmutableCollectionResolver.Instance,
// FSharpResolver.Instance,
// StandardResolver.Instance
)

AppDomain.CurrentDomain.UnhandledException.Add(fun exArgs ->
let filename = Path.Combine(config.configdir, sprintf "fvim-crash-%s.txt" (DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")))
use dumpfile = new StreamWriter(filename)
dumpfile.WriteLine(sprintf "Unhandled exception: (terminating:%A)" exArgs.IsTerminating)
dumpfile.WriteLine(exArgs.ExceptionObject.ToString())
)
System.Console.OutputEncoding <- System.Text.Encoding.Unicode

let opts = parseOptions args
FVim.log.init opts
match opts.intent with
| Setup -> setup()
| Uninstall -> uninstall()
| Daemon(port, pipe) -> daemon port pipe opts
| Start ->

// Avalonia initialization
let builder = buildAvaloniaApp()
let lifetime = new ClassicDesktopStyleApplicationLifetime()
lifetime.ShutdownMode <- Controls.ShutdownMode.OnMainWindowClose
let _ = builder.SetupWithLifetime(lifetime)
// Avalonia is initialized. SynchronizationContext-reliant code should be working by now;

let model_start =
try
Model.Start opts
Ok()
with ex -> Error ex

match model_start with
| Ok() ->
let cfg = config.load()
let cwd = Environment.CurrentDirectory |> Path.GetFullPath
let workspace = cfg.Workspace |> Array.tryFind(fun w -> w.Path = cwd)
workspace
>>= fun workspace -> workspace.Mainwin.BackgroundComposition
>>= fun comp -> States.parseBackgroundComposition(box comp)
>>= fun comp -> States.background_composition <- comp; None
|> ignore

let mainwin = new MainWindowViewModel(workspace)
lifetime.MainWindow <- MainWindow(DataContext = mainwin)
let ret = lifetime.Start(args)

config.save cfg (int mainwin.X) (int mainwin.Y) (mainwin.Width) (mainwin.Height) (mainwin.WindowState) (States.backgroundCompositionToString States.background_composition) mainwin.CustomTitleBar
ret
| Error ex ->
let crash = new CrashReportViewModel(ex)
lifetime.MainWindow <- new CrashReport(DataContext = crash)
ignore <| lifetime.Start(args)
-1
let _ = Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA)

CompositeResolver.RegisterAndSetAsDefault(
MsgPackResolver()
// ImmutableCollectionResolver.Instance,
// FSharpResolver.Instance,
// StandardResolver.Instance
)

AppDomain.CurrentDomain.UnhandledException.Add(fun exArgs ->
let filename = Path.Combine(config.configdir, sprintf "fvim-crash-%s.txt" (DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")))
use dumpfile = new StreamWriter(filename)
dumpfile.WriteLine(sprintf "Unhandled exception: (terminating:%A)" exArgs.IsTerminating)
dumpfile.WriteLine(exArgs.ExceptionObject.ToString())
)
System.Console.OutputEncoding <- System.Text.Encoding.Unicode

let opts = parseOptions args
FVim.log.init opts
match opts.intent with
| Setup -> setup()
| Uninstall -> uninstall()
| Daemon(port, pipe) -> daemon port pipe opts
| Start ->

// Avalonia initialization
let builder = buildAvaloniaApp()
let lifetime = new ClassicDesktopStyleApplicationLifetime()
lifetime.ShutdownMode <- Controls.ShutdownMode.OnMainWindowClose
let _ = builder.SetupWithLifetime(lifetime)
// Avalonia is initialized. SynchronizationContext-reliant code should be working by now;

try
Model.Start opts
Ok()
with ex -> Error ex
|> function
| Ok() ->
let cfg = config.load()
let cwd = Environment.CurrentDirectory |> Path.GetFullPath
let workspace = cfg.Workspace |> Array.tryFind(fun w -> w.Path = cwd)
workspace
>>= fun workspace -> workspace.Mainwin.BackgroundComposition
>>= fun comp -> States.parseBackgroundComposition(box comp)
>>= fun comp -> States.background_composition <- comp; None
|> ignore

let mainwin = new MainWindowViewModel(workspace)
lifetime.MainWindow <- MainWindow(DataContext = mainwin)
try
ignore <| lifetime.Start(args)
config.save cfg (int mainwin.X) (int mainwin.Y) (mainwin.Width) (mainwin.Height) (mainwin.WindowState) (States.backgroundCompositionToString States.background_composition) mainwin.CustomTitleBar
Ok()
with ex -> Error ex
| Error ex -> Error ex
|> function
| Ok() -> 0
| Error ex ->
// TODO should yield so that the crash info can propagate back
FVim.log.trace "main" "displaying crash dialog"
let code, msgs = States.get_crash_info()
let crash = new CrashReportViewModel(ex, code, msgs)
let win = new CrashReport(DataContext = crash)
lifetime.MainWindow <- win
ignore <| lifetime.Start(args)
-1
6 changes: 4 additions & 2 deletions ViewModels/CrashReportViewModel.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ open ui
open log
open common

type CrashReportViewModel(ex: exn) =
type CrashReportViewModel(ex: exn, code: int, msgs: ResizeArray<string>) =
inherit ViewModelBase()
member __.MainMessage =
ex.Message
sprintf "Exit code: %d\n" code +
ex.Message + "\n" +
join msgs
member __.StackTrace =
ex.StackTrace.Split("\n")
member __.TipMessage =
Expand Down
31 changes: 21 additions & 10 deletions neovim.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Nvim() =
let mutable m_events = None
let mutable m_io = Disconnected
let mutable m_disposables = []
let m_cancelSrc = new CancellationTokenSource()

member __.Id = Guid.NewGuid()

Expand Down Expand Up @@ -121,19 +122,24 @@ type Nvim() =
| :? System.IO.IOException
| :? System.Net.Sockets.SocketException
| :? ObjectDisposedException
as _ex ->
ex <- true
trace "exception: %O" _ex
as _ex -> ex <- true

let ec = serverExitCode()
if ec.IsSome then
let code = ec.Value
trace "end read loop: process exited, code = %d" code
if code <> 0 then
ob.OnNext([|box(Crash code)|])
let code = ec.Value
trace "end read loop: process exited, code = %d" code
if code <> 0 then
m_cancelSrc.Cancel()
ob.OnNext([|box (Crash code)|])
else
ob.OnNext([|box Exit|])
else
trace "end read loop."
trace "end read loop."
ob.OnNext([|box Exit|])

ob.OnCompleted()


, cancel, TaskCreationOptions.LongRunning, TaskScheduler.Current)

let reply (id: int) (rsp: Response) = async {
Expand Down Expand Up @@ -186,7 +192,6 @@ type Nvim() =
let stdout =
_startRead()
|> Observable.filter intercept
|> Observable.concat (Observable.single Exit)

let events = Observable.merge stdout stderr

Expand All @@ -205,6 +210,8 @@ type Nvim() =
if not <| pending.TryAdd(myid, src)
then failwith "call: cannot create call request"

use cancel_reg = m_cancelSrc.Token.Register(fun () -> src.TrySetCanceled() |> ignore)

let payload = mkparams4 0 myid ev.method ev.parameters
MessagePackSerializer.ToJson(payload) |> trace "call: %d -> %s" myid
do! MessagePackSerializer.SerializeAsync(stdin, payload)
Expand All @@ -225,7 +232,11 @@ type Nvim() =

member __.stop (timeout: int) =

// Disconnect all the subscriptions first
// Send cancellation signal
m_cancelSrc.Cancel()
m_cancelSrc.Dispose()

// Disconnect all the subscriptions
for (d: IDisposable) in m_disposables do
d.Dispose()

Expand Down
22 changes: 16 additions & 6 deletions states.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Event =

let private _stateChangeEvent = Event<string>()
let private _appLifetime = Avalonia.Application.Current.ApplicationLifetime :?> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime
let mutable private _crashcode = 0
let private _errormsgs = ResizeArray<string>()

// request handlers are explicitly registered, 1:1, with no broadcast.
let requestHandlers = hashmap[]
Expand Down Expand Up @@ -198,6 +200,9 @@ let backgroundCompositionToString =

let Shutdown code = _appLifetime.Shutdown code

let get_crash_info() =
_crashcode, _errormsgs

let msg_dispatch =
function
| Request(id, req, reply) ->
Expand All @@ -216,12 +221,17 @@ let msg_dispatch =
let event = getNotificationEvent req.method
try event.Trigger req.parameters
with | Failure msg -> error "rpc" "notification trigger [%s] failed: %s" req.method msg
| Exit -> _appLifetime.Shutdown()
| Crash code ->
trace "model" "neovim crashed with code %d" code
_appLifetime.Shutdown()
| Error err -> error "model" "neovim: %s" err
| _ -> ()
| Error err ->
error "rpc" "neovim: %s" err
_errormsgs.Add err
| Exit ->
trace "rpc" "shutting down application lifetime"
_appLifetime.Shutdown()
| Crash code ->
trace "rpc" "neovim crashed with code %d" code
_crashcode <- code
| other ->
trace "rpc" "unrecognized event: %A" other

module Register =
let Request name fn =
Expand Down

0 comments on commit 83a7798

Please sign in to comment.