pyinfra-dev / pyinfra

pyinfra turns Python code into shell commands and runs them on your servers. Execute ad-hoc commands and write declarative operations. Target SSH servers, local machine and Docker containers. Fast and scales from one server to thousands.
https://pyinfra.com
MIT License
3.85k stars 374 forks source link

CycleError with loop and more than 1 server #859

Closed julienlavergne closed 1 year ago

julienlavergne commented 2 years ago

Describe the bug

When running operations in a loop on more than 1 server, there is cases that creates a "graphlib.CycleError". This is due to the fact that we put every operations of every host in the same graph and check for cycle. The way operation hash is generated may create cycle.

To Reproduce

I went to great length to understand the issue and ended up with a minimal example. The random is here to represent the result of a get_fact that is different on each host. But simply putting a conditional get_fact in a loop can triggers the issue.

Run below example on 2 hosts or more:

import random

from pyinfra.operations import server

for i in range(0, 10):
    server.shell(name=f"ls A", commands="ls")

    if random.randint(0, 1) == 0:
        server.shell(name=f"ls B", commands="ls")

    server.shell(name=f"ls C", commands="ls")

Expected behavior

There is no cycle in the above example.

Meta

Fizzadar commented 2 years ago

Hi @julienlavergne thank you for raising this and getting a reprodution example up!

The single DAG is a requirement for having all hosts execute in expected order, but loops once again break things!

Fizzadar commented 2 years ago

I have dug into this today and written up my thoughts here: https://github.com/Fizzadar/pyinfra/blob/850b443a95269df5758a0bd35e34f651b8b444ba/docs/deploy-process.rst#loops--cycle-errors

I have also PR'd a workaround for this in https://github.com/Fizzadar/pyinfra/pull/876.

Unfortunately I cannot think of any way to achieve this without an explicit workaround - there's just no way [I can think of] to pass through the loop position automatically...

julienlavergne commented 2 years ago

I added a comment to the PR

Fizzadar commented 1 year ago

This should now be resolved in v2.5 once pypi is back up to complete the release!