I'm having trouble finding good documentation for the gcroot command as it applies to .NET core code so it's making it hard to follow some very weird gcroot output I have.
Below you'll see the top part of the output I'm talking about and the very weird route it takes. I didn't post the entire thing as it just keeps being very weird but is there some sort of logic one can use to figure out which of these actually apply? It almost looks like things that both reference the same thing are being included on the path.
rbp-8: 00007ffcdac29f18
-> 00007F47D41DDCA8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions+<RunAsync>d__4, Microsoft.Extensions.Hosting.Abstractions]]
-> 00007F48D40E9560 Microsoft.Extensions.Hosting.Internal.Host
-> 00007F48D40E8380 Microsoft.Extensions.Logging.Logger`1[[Microsoft.Extensions.Hosting.Internal.Host, Microsoft.Extensions.Hosting]]
-> 00007F48D40E8478 Microsoft.Extensions.Logging.Logger
-> 00007F48D40E84A0 Microsoft.Extensions.Logging.LoggerInformation[]
-> 00007F48D40E8718 NLog.Extensions.Logging.NLogLogger
-> 00007F48D40E8500 NLog.Logger
-> 00007F48D40E86E0 NLog.Internal.LoggerConfiguration
-> 00007F48D40E8548 NLog.Internal.TargetWithFilterChain[]
-> 00007F48D40E8590 NLog.Internal.TargetWithFilterChain
-> 00007F47D4102560 NLog.Targets.Wrappers.AsyncTargetWrapper
-> 00007F47D4159DD0 System.Collections.Generic.List`1[[NLog.Layouts.Layout, NLog]]
-> 00007F47D4159DF0 NLog.Layouts.Layout[]
-> 00007F47D4102268 NLog.Layouts.SimpleLayout
-> 00007F47D40F1E48 NLog.Config.XmlLoggingConfiguration
-> 00007F47D40F1F28 System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[NLog.Targets.Target, NLog]]
-> 00007F47D411DC68 System.Collections.Generic.Dictionary`2+Entry[[System.String, System.Private.CoreLib],[NLog.Targets.Target, NLog]][]
-> 00007F47D410D798 NLog.Targets.Wrappers.AsyncTargetWrapper
-> 00007F47D4103BE0 NLog.Targets.FileTarget
-> 00007F47D412C038 NLog.Internal.FileAppenders.FileAppenderCache
-> 00007F47D412C180 System.Threading.Timer
-> 00007F47D412C1F8 System.Threading.TimerHolder
-> 00007F47D412C198 System.Threading.TimerQueueTimer
-> 00007F47D4049098 System.Threading.TimerQueue
-> 00007F4824900220 System.Threading.TimerQueueTimer
-> 00007F47D4048FC0 System.Threading.TimerQueueTimer
-> 00007F482732D6B8 System.Threading.TimerQueueTimer
-> 00007F492797C860 System.Threading.TimerQueueTimer
-> 00007F4927978F28 System.Threading.TimerQueueTimer
-> 00007F47D48F1BD8 System.Threading.TimerQueueTimer
-> 00007F4924F62E10 System.Threading.TimerQueueTimer
-> 00007F492735E210 System.Threading.TimerQueueTimer
-> 00007F47D40DF710 System.Threading.TimerQueueTimer
-> 00007F4926FE91F8 System.Threading.TimerQueueTimer
-> 00007F4926FE71D8 System.Threading.TimerQueueTimer
-> 00007F482662DA08 System.Threading.TimerQueueTimer
-> 00007F49215F3B70 System.Threading.TimerQueueTimer
-> 00007F48D41C20E0 System.Threading.TimerQueueTimer
-> 00007F48252EEAF0 System.Threading.TimerQueueTimer
-> 00007F48D80F1E48 System.Threading.TimerQueueTimer
-> 00007F481925FC30 System.Threading.TimerQueueTimer
-> 00007F481925FBD8 System.Threading.TimerCallback
-> 00007F481925F080 Microsoft.Data.ProviderBase.DbConnectionPool
-> 00007F47D48F1A70 Microsoft.Data.SqlClient.SqlConnectionFactory
-> 00007F492795E7A8 System.Collections.Generic.Dictionary`2[[Microsoft.Data.Common.DbConnectionPoolKey, Microsoft.Data.SqlClient],[Microsoft.Data.ProviderBase.DbConnectionPoolGroup, Microsoft.Data.SqlClient]]
-> 00007F492795E820 System.Collections.Generic.Dictionary`2+Entry[[Microsoft.Data.Common.DbConnectionPoolKey, Microsoft.Data.SqlClient],[Microsoft.Data.ProviderBase.DbConnectionPoolGroup, Microsoft.Data.SqlClient]][]
-> 00007F47D48F3D38 Microsoft.Data.ProviderBase.DbConnectionPoolGroup
<snip>
Related
I'm investigating a webapp which ran up to 10gb of memory, by analysing a memory dump using Windbg.
Here's the bottom of the !dumpheap -stat output:
00007ff9545df5d0 166523 13321840 System.Runtime.Caching.MemoryCache
00007ff9545df4a0 166523 14654024 System.Runtime.Caching.CacheMemoryMonitor
00007ff9545de990 166523 14654024 System.Runtime.Caching.SRef[]
00007ff9545dcef0 166523 14654024 System.Runtime.Caching.GCHandleRef`1[[System.Runtime.Caching.MemoryCacheStore, System.Runtime.Caching]][]
00007ff9545dfb28 166523 19982760 System.Runtime.Caching.MemoryCacheStatistics
00007ff956778510 333059 21315680 System.Int64[]
00007ff95679c988 41597 31250111 System.Byte[]
00007ff9545e08c8 1332184 31972416 System.Runtime.Caching.MemoryCacheEqualityComparer
00007ff9545dfe48 1332184 31972416 System.Runtime.Caching.SRef
00007ff956780ff0 1332200 31972800 System.SizedReference
00007ff956724620 1498777 35970648 System.Threading.TimerHolder
00007ff95677fb30 1536170 36868080 System.Runtime.Remoting.Messaging.CallContextSecurityData
00007ff956796f28 1606960 38567040 System.Object
00007ff9545df810 1332184 42629888 System.Runtime.Caching.GCHandleRef`1[[System.Runtime.Caching.MemoryCacheStore, System.Runtime.Caching]]
00007ff9545dda38 1332184 42629888 System.Runtime.Caching.UsageBucket[]
00007ff9567ae930 1332268 42632576 Microsoft.Win32.SafeHandles.SafeWaitHandle
00007ff9545df968 1498707 47958624 System.Runtime.Caching.GCHandleRef`1[[System.Threading.Timer, mscorlib]]
00007ff9567adbf8 1498777 47960864 System.Threading.Timer
00007ff9545dff50 1332184 53287360 System.Runtime.Caching.CacheUsage
00007ff94986ead8 1536137 61445480 System.Web.Hosting.AspNetHostExecutionContextManager+AspNetHostExecutionContext
00007ff9567a2838 1332210 63946080 System.Threading.ManualResetEvent
00007ff956796948 293525 66384986 System.String
00007ff9545dfef0 1332184 74602304 System.Runtime.Caching.CacheExpires
00007ff9567add20 1498760 95920640 System.Threading.TimerCallback
00007ff9545dfa90 1332184 106574720 System.Runtime.Caching.MemoryCacheStore
00007ff95679b3b0 1333289 106663120 System.Collections.Hashtable
00007ff95678f138 1536171 110604312 System.Runtime.Remoting.Messaging.LogicalCallContext
00007ff9545dffb0 1332184 127889664 System.Runtime.Caching.UsageBucket
00007ff95679d1e0 1333292 128664768 System.Collections.Hashtable+bucket[]
00007ff9567245c0 1498777 131892376 System.Threading.TimerQueueTimer
00007ff9567aec48 1536255 135190440 System.Threading.ExecutionContext
00007ff9545dcf78 1332184 351696576 System.Runtime.Caching.ExpiresBucket[]
000000f82c79d9f0 473339 385303992 Free
00007ff956799220 40309535 1617342672 System.Int32[]
00007ff9545e0468 39965520 3836689920 System.Runtime.Caching.ExpiresBucket
So there are nearly 40 million instances of System.Runtime.Caching.ExpiresBucket, totally nearly 4gb of the used memory. System.Runtime.Caching classes appear quite a lot in the top offenders.
I took a random instance of a System.Runtime.Caching.ExpiresBucket class, and did a !gcroot on it. It took ages (maybe 30 mins) to produce 1 thread...there may have been more but I interrupted the operation at this point.
The chain of references is over 1.5 million lines long! But I can show the important bits here:
0:000> !gcroot 000000f82dd4bc28
Thread 1964:
000000fcbdbce6a0 00007ff8f9bbe388 Microsoft.AspNet.SignalR.SqlServer.ObservableDbOperation.ExecuteReaderWithUpdates(System.Action`2<System.Data.IDataRecord,Microsoft.AspNet.SignalR.SqlServer.DbOperation>)
rbp-58: 000000fcbdbce6e8
-> 000000fa2d1f26a0 Microsoft.AspNet.SignalR.SqlServer.ObservableDbOperation+<>c__DisplayClass1e
-> 000000fa2d1f2110 Microsoft.AspNet.SignalR.SqlServer.ObservableDbOperation
-> 000000fa2d1f24d0 System.Action
-> 000000fa2d1f24a8 System.Object[]
-> 000000fa2d1f2468 System.Action
-> 000000fa2d1f1008 Microsoft.AspNet.SignalR.SqlServer.SqlReceiver
-> 000000fa2d1f1330 System.Action
-> 000000fa2d1f1308 System.Object[]
-> 000000fa2d1f12c8 System.Action
-> 000000fa2d1efb70 Microsoft.AspNet.SignalR.SqlServer.SqlStream
-> 000000fa2d1f1528 System.Action
-> 000000fa2d1f1500 System.Object[]
-> 000000fa2d1f14c0 System.Action
-> 000000fa2d1efb20 Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus+<>c__DisplayClass3
-> 000000f92d0b84e0 Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus
-> 000000f92d0b9568 System.Threading.Timer
-> 000000f92d0b96d8 System.Threading.TimerHolder
-> 000000f92d0b95a0 System.Threading.TimerQueueTimer
[... about 100 lines of the same TimerQueueTimer line above, but different memory addresses each time]
-> 000000f92cf1be68 System.Threading.TimerQueueTimer
-> 000000f92cf1be08 System.Threading.TimerCallback
-> 000000f92cf1bb48 System.Web.RequestTimeoutManager
-> 000000f92cf1bb80 System.Web.Util.DoubleLinkList[]
-> 000000f92cf1bc00 System.Web.Util.DoubleLinkList
-> 000000fb61323860 System.Web.RequestTimeoutManager+RequestTimeoutEntry
-> 000000fb6131fd38 System.Web.HttpContext
-> 000000fbe682a480 ASP.global_asax
-> 000000fbe682ac00 System.Web.HttpModuleCollection
-> 000000fbe682ac60 System.Collections.ArrayList
-> 000000fbe682b598 System.Object[]
-> 000000fbe682b018 System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
-> 000000fbe682b000 System.Web.Routing.UrlRoutingModule
-> 000000faacec1f40 System.Web.Routing.RouteCollection
-> 000000faacec2030 System.Collections.Generic.List`1[[System.Web.Routing.RouteBase, System.Web]]
-> 000000fa2cfe4d80 System.Web.Routing.RouteBase[]
-> 000000f9acf14cd8 System.Web.Http.WebHost.Routing.HttpWebRoute
-> 000000f9acf149f8 System.Web.Http.Routing.RouteCollectionRoute
-> 000000f9acf1f4f0 System.Web.Http.Routing.SubRouteCollection
-> 000000f9acf1f510 System.Collections.Generic.List`1[[System.Web.Http.Routing.IHttpRoute, System.Web.Http]]
-> 000000fa2cf8f310 System.Web.Http.Routing.IHttpRoute[]
-> 000000fa2ceff770 System.Web.Http.Routing.HttpRoute
-> 000000fa2ceff678 System.Web.Http.Routing.HttpRouteValueDictionary
-> 000000fa2ceff6f0 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Object, mscorlib]][]
-> 000000fa2cef9e78 System.Web.Http.Controllers.HttpActionDescriptor[]
-> 000000fa2cef7898 System.Web.Http.Controllers.ReflectedHttpActionDescriptor
-> 000000f9aced4608 System.Web.Http.HttpConfiguration
-> 000000f9aced4db0 System.Net.Http.Formatting.MediaTypeFormatterCollection
-> 000000f9aced6f40 System.Collections.Generic.List`1[[System.Net.Http.Formatting.MediaTypeFormatter, System.Net.Http.Formatting]]
-> 000000f9aced6f80 System.Net.Http.Formatting.MediaTypeFormatter[]
-> 000000f9aced4df8 System.Net.Http.Formatting.JsonMediaTypeFormatter
-> 000000f9acf1f448 System.Web.Http.Validation.ModelValidationRequiredMemberSelector
-> 000000f9acf1f468 System.Collections.Generic.List`1[[System.Web.Http.Validation.ModelValidatorProvider, System.Web.Http]]
-> 000000f9acf1f490 System.Web.Http.Validation.ModelValidatorProvider[]
-> 000000f9acf1db40 Ninject.Web.WebApi.Validation.NinjectDefaultModelValidatorProvider
-> 000000faaceca438 Ninject.StandardKernel
-> 000000faaceca498 Ninject.Components.ComponentContainer
-> 000000faaceca538 System.Collections.Generic.Dictionary`2[[System.Type, mscorlib],[Ninject.Components.INinjectComponent, Ninject]]
-> 000000f9acece000 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[Ninject.Components.INinjectComponent, Ninject]][]
-> 000000f9acecdac8 Ninject.Activation.Caching.GarbageCollectionCachePruner
-> 000000f9acecdcb8 System.Threading.Timer
-> 000000f9acecdd30 System.Threading.TimerHolder
-> 000000f9acecdcd8 System.Threading.TimerQueueTimer
[... just under 1.5 million lines of the same TimerQueueTimer line above, but different memory addresses each time]
-> 000000f82dd4c028 System.Threading.TimerQueueTimer
-> 000000f82dd4bfc8 System.Threading.TimerCallback
-> 000000f82dd4ada0 System.Runtime.Caching.CacheExpires
-> 000000f82dd4add8 System.Runtime.Caching.ExpiresBucket[]
-> 000000f82dd4bc28 System.Runtime.Caching.ExpiresBucket
I'm running !objsize on the 000000f92d0b84e0 Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus, but it's been running for about 20 mins so far, and I'm expecting a very high number.
What is causing the leak? Is there a known bug in SignalR, using Backplane? We're using v2.0.2, which I believe is not the latest. But I've not found any documentation on the internet referring to a memory leak involving both SignalR and Caching.
Edit:
To answer Pawel's question about instances of SqlMessageBus, here is some more Windbg output:
0:000> !dumpheap -type SqlMessageBus
Address MT Size
0000007ced6832a0 00007ff8f9edb708 24
0000007ced6832b8 00007ff8f9a97da8 64
0000007ced6832f8 00007ff8f9a97c90 248
0000007ced685370 00007ff8f9edbc18 24
0000007d6d917f48 00007ff8f9a97ec8 40
0000007feda7c400 00007ff8f9e88b98 32
Statistics:
MT Count TotalSize Class Name
00007ff8f9edbc18 1 24 System.Lazy`1+Boxed[[Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus, Microsoft.AspNet.SignalR.SqlServer]]
00007ff8f9edb708 1 24 System.Lazy`1+<>c[[Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus, Microsoft.AspNet.SignalR.SqlServer]]
00007ff8f9e88b98 1 32 Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus+<>c__DisplayClass3
00007ff8f9a97ec8 1 40 System.Lazy`1[[Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus, Microsoft.AspNet.SignalR.SqlServer]]
00007ff8f9a97da8 1 64 System.Func`1[[Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus, Microsoft.AspNet.SignalR.SqlServer]]
00007ff8f9a97c90 1 248 Microsoft.AspNet.SignalR.SqlServer.SqlMessageBus
In our case, the reason in the end was because we had a class that was creating a new instance of MemoryCache in its constructor... and we were supposed to be binding that class as a Singleton, but we weren't. So essentially we were ending up with hundreds of thousands of MemoryCache instances.
I'm trying to make a recursive call to handle_call/3 before replying.
But it seems to not be possible, because a timeout exit exception is thrown.
Below you can see the code and the error.
The code:
-module(test).
-behavior(gen_server).
%% API
-export([start_link/0,init/1,handle_info/2,first_call/1,second_call/1, handle_call/3, terminate/2]).
-record(state, {whatever}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_Args) ->
{ok, #state{}}.
handle_info(Data, State) ->
{noreply, State}.
% synchronous messages
handle_call(_Request, _From, State) ->
case _Request of
first_call ->
{reply, data1, State};
second_call ->
{reply, {data2, first_call(self())}, State}
end.
first_call(Pid) ->
gen_server:call(Pid, first_call).
second_call(Pid) ->
gen_server:call(Pid, second_call).
terminate(_Reason, _State) ->
ok.
The error:
2> {_, PID} = test:start_link().
{ok,<0.64.0>}
3> test:second_call(PID).
** exception exit: {timeout,{gen_server,call,[<0.64.0>,second_call]}}
in function gen_server:call/2 (gen_server.erl, line 204)
A possible reason for this behaviour is that gen_server is not able to handle recursive calls until the first one is done (creating a deadlock). Is this the right reason, and if yes why?
Thanks.
Yes, that is the reason. The gen_server is a single process, and while handle_call is executing it doesn't pick up any messages nor respond to any gen_server:call. Therefore first_call(self()) times out.
I'm currently trying to create a simple chat server with socket.io-erlang. I just started learning Erlang so I have a few problems adapting their demo so it works with modules. Hope you can help me, here's what I have so far. It shouldn't have any features yet, this time I only want to make it work (I get a few crash reports after starting it, you can read them if you want).
App
-module(echat_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
echat_sup:start_link().
stop(_State) ->
ok.
Supervisor
-module(echat_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
application:start(sasl),
application:start(gproc),
application:start(misultin),
application:start(socketio),
{ok, Pid} = socketio_listener:start([{http_port, 7878}, {default_http_handler,echat_http_handler}]),
EventManager = socketio_listener:event_manager(Pid),
ok = gen_event:add_handler(EventManager, echat_socketio_handler,[]),
receive _ ->
io:format("sub received something"),
{ok, {
{one_for_one, 5, 10},
[]
}}
end.
Socket.IO Event Handler
-module(echat_socketio_handler).
-behaviour(gen_event).
-include_lib("socketio/include/socketio.hrl").
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]).
init([]) ->
{ok, undefined}.
handle_event({client, Pid}, State) ->
io:format("Connected: ~p~n",[Pid]),
EventManager = socketio_client:event_manager(Pid),
ok = gen_event:add_handler(EventManager, ?MODULE,[]),
{ok, State};
handle_event({disconnect, Pid}, State) ->
io:format("Disconnected: ~p~n",[Pid]),
{ok, State};
handle_event({message, Client, Msg=#msg{content=Content}}, State) ->
io:format("Got a message: ~p from ~p~n",[Msg, Client]),
socketio_client:send(Client, #msg{ content = "hello!" }),
socketio_client:send(Client, #msg{ content = [{<<"echo">>, Content}], json = true}),
{ok, State};
handle_event(_E, State) ->
{ok, State}.
handle_call(_, State) ->
{reply, ok, State}.
handle_info(_, State) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
HTTP Request Handler
-module(echat_http_handler).
-export([handle_request/3]).
handle_request(_Method, _Path, Req) ->
Req:respond(200).
just a few things, no a specific answer to your question.
First, in general you start the dependencies before starting your app, rather than on the init of some supervisor.
Regarding the errors:
{error,{{noproc,{gen_server,call,
[socketio_listener_sup_sup,
{start_child,[[{http_port,7878},
{default_http_handler,echat_http_handler}]]},
infinity]}},
{echat_app,start,[normal,[]]}}}
This is the first one, it means that the code tried to call a gen_server named 'socketio_listener_sup_sup' , but that process did not exist. Given the name of it, I guess that is something that should be started by the socket.io-erlang application itself, so maybe it is not starting correctly for some reason. You can check that easily by checking the results:
ok = application:start(sasl),
ok = application:start(gproc),
ok = application:start(misultin),
ok = application:start(socketio),
I am starting to learn Erlang in the hopes of creating a game server to real-time multiplayer games. Currently, I am trying to estimate the amount of work and headache Erlang would cause vs. Scala. So, to start, I am creating a simple Erlang server process. I found a nice tutorial by Jesse Farmer which I have modified to learn more. My modified code is meant to be similar to his echo server, except it takes in English words and simply returns the Lojban equivalent. However, only the wildcard case is ever selected. Here is the code:
-module(translate).
-export([listen/1]).
-import(string).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
% Call echo:listen(Port) to start the service.
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
% Wait for incoming connections and spawn the echo loop when we get one.
accept(LSocket) ->
{ok, Socket} = gen_tcp:accept(LSocket),
spawn(fun() -> loop(Socket) end),
accept(LSocket).
% Echo back whatever data we receive on Socket.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
case Data of
"Hello" -> gen_tcp:send(Socket, "coi\n");
"Hello\n" -> gen_tcp:send(Socket, "coi\n");
'Hello' -> gen_tcp:send(Socket, "coi\n");
<<"Hello">> -> gen_tcp:send(Socket, "coi\n");
<<"Hello\n">> -> gen_tcp:send(Socket, "coi\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
loop(Socket);
{error, closed} ->
ok
end.
My current test is to open two terminal windows and execute
[CONSOLE 1]
erl
c(translate).
translate:listen(8888).
[CONSOLE 2]
telnet localhost 8888
whatever
Hello
And the output becomes:
I don't understand
I don't understand
How can I parse the incoming data? This style of pattern matching seems to be failing completely. Thanks!
Try this one:
case binary_to_list(Data) of
"Hello\r\n" -> gen_tcp:send(Socket, "this will be good variant\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
Or without explicit convert:
case Data of
<<"Hello\r\n">> -> gen_tcp:send(Socket, "this will be good variant\n");
_ -> gen_tcp:send(Socket, "I don't understand")
end,
Updated from comments
To work with more complicated matching remove "\r\n" suffix first:
Content = list_to_binary(lists:subtract(binary_to_list(Data), "\r\n")),
case Content of
<<"Hello">> -> gen_tcp:send(Socket, <<"Good day!\n">>);
<<"My name is, ", Name/binary>> -> gen_tcp:send(Socket, <<"Hello ", Name/binary, "!\n">>);
_ -> gen_tcp:send(Socket, "I don't understand")
end,
I have a number of relatively simple (auto-generated) graphs in graphviz dot format. These show the path through a state machine, but dot has a slightly confusing habit of deciding that two nodes must be on the same rank when I would like the graph to be in state order. I've tried a lot of settings (including the :n and :s and the weight listed below), but I cannot persuade dot to place the Third State above the Fourth State.
I have this problem with a lot of graphs: there seems to be something internal to dot that decides that it would be better if two nodes were on the same rank and there's nothing that can be done to override it. I've even had code that specifies that one node should be a rank=sink, but dot has decided to put another node below it anyway.
Is there any way to suggest to dot that it's more important that the nodes are in order than any other constraint?
The code that was used to generate the graph looks like this:
digraph {
ERROR [label="Error"];
FirstSTATE [label="Initial State" URL="\ref FirstSTATE"];
FirstSTATE -> SecondSTATE;
SecondSTATE [label="Second State" URL="\ref SecondSTATE"];
SecondSTATE -> ThirdSTATE;
ThirdSTATE [label="Third State" URL="\ref ThirdSTATE"];
FourthSTATE [label="Fouth State?" shape="diamond"];
ThirdSTATE:s -> FourthSTATE:n [weight=50];
FourthSTATE -> FifthSTATE [label="Yes" ];
FourthSTATE -> ThirdSTATE [label="No"];
FifthSTATE [label="Fifth State" URL="\ref FifthSTATE"];
SixthSTATE [label="Sixth State?" shape="diamond"];
SixthSTATE -> ERROR [label="Yes" ];
SixthSTATE -> SeventhSTATE [label="No"];
FifthSTATE -> SixthSTATE;
SeventhSTATE [label="Seventh State" URL="\ref SeventhSTATE"];
SeventhSTATE -> EighthSTATE;
EighthSTATE [label="Eighth State" URL="\ref EighthSTATE"];
NinthSTATE [label="Ninth State?" shape="diamond"];
NinthSTATE -> TenthSTATE [label="Yes" ];
NinthSTATE -> EighthSTATE [label="No"];
EighthSTATE -> NinthSTATE;
TenthSTATE [label="Tenth State" URL="\ref TenthSTATE"];
EleventhSTATE [label="Eleventh State?" shape="diamond"];
EleventhSTATE -> ERROR [label="Yes" ];
EleventhSTATE -> TwelfthSTATE [label="No" ];
TenthSTATE -> EleventhSTATE;
TwelfthSTATE [label="Twelfth State" URL="\ref TwelfthSTATE"];
}
The graph currently looks like this:
Use "constraint=false".
http://www.graphviz.org/doc/info/attrs.html#d:constraint
In your graph:
FourthSTATE -> ThirdSTATE [label="No" constraint=false] ;
You'll get:
digraph {
ERROR [label="Error"];
FirstSTATE [label="Initial State" URL="\ref FirstSTATE"];
FirstSTATE -> SecondSTATE;
SecondSTATE [label="Second State" URL="\ref SecondSTATE"];
SecondSTATE -> ThirdSTATE;
ThirdSTATE [label="Third State" URL="\ref ThirdSTATE"];
FourthSTATE [label="Fouth State?" shape="diamond"];
ThirdSTATE -> FourthSTATE;
FourthSTATE -> FifthSTATE [label="Yes" ];
FourthSTATE -> ThirdSTATE [label="No" constraint=false] ;
FifthSTATE [label="Fifth State" URL="\ref FifthSTATE"];
SixthSTATE [label="Sixth State?" shape="diamond"];
SixthSTATE -> ERROR [label="Yes" ];
SixthSTATE -> SeventhSTATE [label="No"];
FifthSTATE -> SixthSTATE;
SeventhSTATE [label="Seventh State" URL="\ref SeventhSTATE"];
SeventhSTATE -> EighthSTATE;
EighthSTATE [label="Eighth State" URL="\ref EighthSTATE"];
NinthSTATE [label="Ninth State?" shape="diamond"];
NinthSTATE -> TenthSTATE [label="Yes" ];
NinthSTATE -> EighthSTATE [label="No"];
EighthSTATE -> NinthSTATE;
TenthSTATE [label="Tenth State" URL="\ref TenthSTATE"];
EleventhSTATE [label="Eleventh State?" shape="diamond"];
EleventhSTATE -> ERROR [label="Yes" ];
EleventhSTATE -> TwelfthSTATE [label="No" ];
TenthSTATE -> EleventhSTATE;
TwelfthSTATE [label="Twelfth State" URL="\ref TwelfthSTATE"];
}
Whenever you want an arrow that points upward, write the edge from top to bottom (i.e. backwards), then add dir=back (https://graphviz.org/docs/attrs/dir/) to make the arrow point upward instead of downward.
So instead of:
FourthSTATE -> ThirdSTATE [label="No"];
write:
ThirdSTATE -> FourthSTATE [dir=back, label="No"];
Now since all the edges go from ThirdSTATE to FourthSTATE, the rank is no longer ambiguous and Graphviz will reliably put ThirdSTATE above FourthSTATE.
This is often better than using constraint=false since edges with constraint=false are sometimes given very wiggly edges (as if they did not participate in graph layout?).