Thread overview
What sorts of things cause cyclic dependencies?
Oct 14, 2010
Jonathan M Davis
Oct 14, 2010
Jonathan M Davis
Oct 14, 2010
bearophile
Oct 14, 2010
spir
October 14, 2010
Okay. in the code that I'm working on at the moment, I get an exception saying that a cyclic dependency was detected but no information whatsoever as to where it is or what it means. I haven't been able to find much information on them other than some discussions of making it so that the compiler detects them at compile time. I have yet to figure out _what_ it is that is causes such errors. Could someone clue me in? It's really hard to track down a bug when the error only tells you that there's a bug and doesn't say anything about where it happens, and I have no clue what sort of things can be cyclically dependent.

- Jonathan M Davis
October 14, 2010
On Wed, 13 Oct 2010 21:25:15 -0700, Jonathan M Davis wrote:

> Okay. in the code that I'm working on at the moment, I get an exception saying that a cyclic dependency was detected but no information whatsoever as to where it is or what it means. I haven't been able to find much information on them other than some discussions of making it so that the compiler detects them at compile time. I have yet to figure out _what_ it is that is causes such errors. Could someone clue me in? It's really hard to track down a bug when the error only tells you that there's a bug and doesn't say anything about where it happens, and I have no clue what sort of things can be cyclically dependent.


Say you have two modules, a and b.  Both have static constructors, and a imports b:

  // a.d
  module a;
  import b;
  static this() { ... }

  // b.d
  module b;
  static this() { ... }

The language is then defined so that b's constructor is run before a's, since a depends on b.  But what happens if b imports a as well?  There is no way to determine which constructor to run first, so the runtime throws a "cyclic dependency" exception.

The way to fix it is to move one of the module constructors into a separate module:

  module a;
  import a_init;
  import b;

  module a_init;
  static this() { ... }

  module b;
  import a;
  static this() { ... }

For a "real" example of how this is done, check out std.stdio and std.stdiobase in Phobos.

-Lars
October 14, 2010
On Thu, 14 Oct 2010 05:53:51 +0000, Lars T. Kyllingstad wrote:

> On Wed, 13 Oct 2010 21:25:15 -0700, Jonathan M Davis wrote:
> 
>> Okay. in the code that I'm working on at the moment, I get an exception saying that a cyclic dependency was detected but no information whatsoever as to where it is or what it means. I haven't been able to find much information on them other than some discussions of making it so that the compiler detects them at compile time. I have yet to figure out _what_ it is that is causes such errors. Could someone clue me in? It's really hard to track down a bug when the error only tells you that there's a bug and doesn't say anything about where it happens, and I have no clue what sort of things can be cyclically dependent.
> 
> 
> Say you have two modules, a and b.  Both have static constructors, and a imports b:
> 
>   // a.d
>   module a;
>   import b;
>   static this() { ... }
> 
>   // b.d
>   module b;
>   static this() { ... }
> 
> The language is then defined so that b's constructor is run before a's, since a depends on b.  But what happens if b imports a as well?  There is no way to determine which constructor to run first, so the runtime throws a "cyclic dependency" exception.


I should mention that this can happen with larger cycles as well, i.e. "a imports b, b imports c, c imports ..., ... imports a".  This can make some cyclic dependencies very hard to track down.

There was a discussion about this on the Phobos mailing list a few months ago:

  http://lists.puremagic.com/pipermail/phobos/2010-June/thread.html#949

-Lars
October 14, 2010
On Wednesday 13 October 2010 23:03:05 Lars T. Kyllingstad wrote:
> On Thu, 14 Oct 2010 05:53:51 +0000, Lars T. Kyllingstad wrote:
> > On Wed, 13 Oct 2010 21:25:15 -0700, Jonathan M Davis wrote:
> >> Okay. in the code that I'm working on at the moment, I get an exception saying that a cyclic dependency was detected but no information whatsoever as to where it is or what it means. I haven't been able to find much information on them other than some discussions of making it so that the compiler detects them at compile time. I have yet to figure out _what_ it is that is causes such errors. Could someone clue me in? It's really hard to track down a bug when the error only tells you that there's a bug and doesn't say anything about where it happens, and I have no clue what sort of things can be cyclically dependent.
> > 
> > Say you have two modules, a and b.  Both have static constructors, and a
> > 
> > imports b:
> >   // a.d
> >   module a;
> >   import b;
> >   static this() { ... }
> > 
> >   // b.d
> >   module b;
> >   static this() { ... }
> > 
> > The language is then defined so that b's constructor is run before a's, since a depends on b.  But what happens if b imports a as well?  There is no way to determine which constructor to run first, so the runtime throws a "cyclic dependency" exception.
> 
> I should mention that this can happen with larger cycles as well, i.e. "a imports b, b imports c, c imports ..., ... imports a".  This can make some cyclic dependencies very hard to track down.
> 
> There was a discussion about this on the Phobos mailing list a few months ago:
> 
>   http://lists.puremagic.com/pipermail/phobos/2010-June/thread.html#949
> 
> -Lars

Well, just knowing that it involves module constructors is definitely useful, though the error message really needs to be improved. Thanks.

- Jonathan M Davis
October 14, 2010
Jonathan M Davis:

> Well, just knowing that it involves module constructors is definitely useful, though the error message really needs to be improved. Thanks.

You may file a bug report, with an example, asking for a better error message (with a better wording), or a much better error message (that lists the cycle too).

Bye,
bearophile
October 14, 2010
On Thu, 14 Oct 2010 02:31:23 -0400, Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Wednesday 13 October 2010 23:03:05 Lars T. Kyllingstad wrote:
>> On Thu, 14 Oct 2010 05:53:51 +0000, Lars T. Kyllingstad wrote:
>> > On Wed, 13 Oct 2010 21:25:15 -0700, Jonathan M Davis wrote:
>> >> Okay. in the code that I'm working on at the moment, I get an  
>> exception
>> >> saying that a cyclic dependency was detected but no information
>> >> whatsoever as to where it is or what it means. I haven't been able to
>> >> find much information on them other than some discussions of making  
>> it
>> >> so that the compiler detects them at compile time. I have yet to  
>> figure
>> >> out _what_ it is that is causes such errors. Could someone clue me  
>> in?
>> >> It's really hard to track down a bug when the error only tells you  
>> that
>> >> there's a bug and doesn't say anything about where it happens, and I
>> >> have no clue what sort of things can be cyclically dependent.
>> >
>> > Say you have two modules, a and b.  Both have static constructors,  
>> and a
>> >
>> > imports b:
>> >   // a.d
>> >   module a;
>> >   import b;
>> >   static this() { ... }
>> >
>> >   // b.d
>> >   module b;
>> >   static this() { ... }
>> >
>> > The language is then defined so that b's constructor is run before  
>> a's,
>> > since a depends on b.  But what happens if b imports a as well?  There
>> > is no way to determine which constructor to run first, so the runtime
>> > throws a "cyclic dependency" exception.
>>
>> I should mention that this can happen with larger cycles as well, i.e. "a
>> imports b, b imports c, c imports ..., ... imports a".  This can make
>> some cyclic dependencies very hard to track down.
>>
>> There was a discussion about this on the Phobos mailing list a few months
>> ago:
>>
>>   http://lists.puremagic.com/pipermail/phobos/2010-June/thread.html#949
>>
>> -Lars
>
> Well, just knowing that it involves module constructors is definitely useful,
> though the error message really needs to be improved. Thanks.

I had mostly implemented a cyclic dependency improvement that actually printed the cycle.  However, I never committed it because I found there was a huge hole in the algorithm that needed to be addressed.  Essentially certain cycles are allowed (even today).

I think it can be done with an O(n^2) space and an O(n) algorithm, but I need to work on it.

BTW, there is a bug report for this problem: http://d.puremagic.com/issues/show_bug.cgi?id=4384

-Steve
October 14, 2010
On Thu, 14 Oct 2010 06:03:05 +0000 (UTC)
"Lars T. Kyllingstad" <public@kyllingen.NOSPAMnet> wrote:

> On Thu, 14 Oct 2010 05:53:51 +0000, Lars T. Kyllingstad wrote:
> 
> > On Wed, 13 Oct 2010 21:25:15 -0700, Jonathan M Davis wrote:
> > 
> >> Okay. in the code that I'm working on at the moment, I get an exception saying that a cyclic dependency was detected but no information whatsoever as to where it is or what it means. I haven't been able to find much information on them other than some discussions of making it so that the compiler detects them at compile time. I have yet to figure out _what_ it is that is causes such errors. Could someone clue me in? It's really hard to track down a bug when the error only tells you that there's a bug and doesn't say anything about where it happens, and I have no clue what sort of things can be cyclically dependent.
> > 
> > 
> > Say you have two modules, a and b.  Both have static constructors, and a imports b:
> > 
> >   // a.d
> >   module a;
> >   import b;
> >   static this() { ... }
> > 
> >   // b.d
> >   module b;
> >   static this() { ... }
> > 
> > The language is then defined so that b's constructor is run before a's, since a depends on b.  But what happens if b imports a as well?  There is no way to determine which constructor to run first, so the runtime throws a "cyclic dependency" exception.
> 
> 
> I should mention that this can happen with larger cycles as well, i.e. "a imports b, b imports c, c imports ..., ... imports a".  This can make some cyclic dependencies very hard to track down.
> 
> There was a discussion about this on the Phobos mailing list a few months ago:
> 
>   http://lists.puremagic.com/pipermail/phobos/2010-June/thread.html#949
> 
> -Lars


I'm totally new to D, but have some real-life experience with circular imports. A typical case is with unit testing. Say you design a parsing lib, with (among others) a module defining Pattern types and another for (parse tree) Nodes, wich are thee results of successful pattern matches.
To test Patterns, no issue, since it already imports Nodes for normal process. But testing Nodes introduces a circular dependance, only for testing, because the ordinary way to produce a node, and thus to be able to test its functionality, is to create a Pattern and run its match method against source text.
2 solutions I know of:
* create fake Pattern types inside the Nodes module, just for testing
* export Nodes' test routines into a third module that imports both Patterns and Nodes, but is not import.
That's probably why many libraries have loads of separated test* unittest modules. But I prefere to have tests locally written in the module itself (which well seems to be the intention of D's testing features).

Some static languages (eg freepascal) allow mutual dependance as long as at least one module (M1) uses imported elements of the other (M2) on the implementation side only (not in the interface). This allows the compiler initially constructing the interface of M1 (without any mention of M2), to be imported by M2. Then, M2 can be fully constructed, and imported into M1 for the needs of its implementation only.


Denis
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com