-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Always reinitialize the charm before processing custom events #952
Comments
I'd like to add to the possible workarounds the property pattern I thought about some time ago: At the moment our charm libs do their observer registration in init, but nobody said that's how it should be. def __init__(self):
# charm libs won't register any observers on init, instead...
self.ingress.register_observers()
self.tls.register_observers()
self.scrape.register_observers()
observe(self.ingress.on_foo, self._on_foo)
observe(self.tls.on_bar, self._on_bar)
observe(self.scrape.on_baz, self._on_baz)
@property
def tls(self):
return Tls(self)
@property
def ingress(self):
return Ingress(self)
@property
def scrape(self):
return MetricsEndpoint(
self,
url=self.ingress.url or socket.getfqdn(),
cert=self.tls.cert
) this would obviate some of the timing and init ordering issues as whenever you need access to scrape, ingress and tls would be reinitialized on the fly. |
To make sure I understand the issue with what @sed-i raises, the problem is that he defined Then yeah I like @PietroPasotti's suggestion - put |
There is something to be said about intuitiveness here too though. The solution feels pretty straight forward, but the problem itself feels easy to miss. It doesn't feel intuitive that Although that really gets to how there are first-class (juju) and second-class (framework) events that sound the same but don't really behave the same. Maybe framework events shouldn't be talked about in the same way as juju events |
@PietroPasotti this is interesting, but I think the same problem still remains even with the property pattern:
This approach makes me think about the garbage collector (yikes). ...Unless I'm missing something? |
yeah there are some technical challenges but nothing that a good singleton wouldn't solve. And maybe objects should be stateless too |
I've commented over at #736 (comment) about why I think re-initing the charm before every Juju event (deferred or otherwise) is a good idea. However, for custom events I don't think this is a good idea, or even feasible. Custom events are emitted by the charm or a charm lib, and when In any case, I don't think this is a good solution for the problem at hand in @sed-i's original message. Knowing that custom events are emitted synchronously during the handling of a hook (just like a Python function call), it's not reasonable to expect that those I think we should be using regular programming techniques to do the updates -- any of the ones @sed-i listed in his original post, or any other techniques for propagating updating things to dependencies. I kind of like the simplicity of passing a self.scrape = MetricsEndpoint(
self,
get_url=lambda: self.ingress.url or socket.getfqdn(),
get_cert=lambda: self.tls.cert,
) Or you could observe the url-changed and cert-changed events and have them call new def __init__(self, *args):
...
self.ingress = Ingress(self)
self.tls = Tls(self)
self.scrape = MetricsEndpoint(self)
self.framework.observe(self.ingress.on.url_changed, self._on_ingress_url_changed)
self.framework.observe(self.tls.on.cert_changed, self._on_tls_cert_changed)
def _on_ingress_url_changed(self, event):
self.scrape.update_url(event.url)
def _on_tls_cert_changed(self, event):
self.scrape.update_cert(event.cert) I guess you could think of the callback method as a "pull" model and the observe method as a "push" model. In any case, I'm planning to close this as Not Planned, but I'll leave it open for a week or so for any further discussion. |
It is mere coincidence that a callable worked for us in the past. There is no way of knowing under what circumstances that callback is used. What if it is used only during
Absolutely. A more complete example would be: def _on_ingress_url_changed(self, event):
self.scrape.update_url(event.url)
self.tls.update_csr(event.url)
def _on_tls_cert_changed(self, event):
self.scrape.update_cert(event.cert) "Holistic on # If you need to change the URL later on, use `update_url`.
# If you need to change the cert later on, use `update_cert`.
# If the URL changed and you're using TLS, be sure to call both `update_url` and `update_cert`. Users would need to translate those 3 instructions above into observers + charm logic. |
I don't disagree that these are challenges. I see them more as charm lib documentation problems than fundamental issues. Per #952 (comment), closing this issue as not planned, but we'll keep chipping away at #736. |
I can respect the technical challenges here, but also think it leads to some unexpected problems. I burned myself today with this because I had a charm that basically keeps track of what happened this execution with I have a feeling there's lots of charm code written expecting a fresh instantiation of the objects on each event and they just haven't noticed the issue. Even something as simple as: class MyCharm:
def __init__():
self.processed_relations = 0
def handle_config_changed():
for [relation in relations if has_valid_data(relation)]:
self.processed_relations += 1
self.emit_status()
def emit_status():
print(f"I have processed {self.processed_relations}") Would work differently on a custom event. Not that this is necessarily a good pattern, but I don't see in docs where we set an expectation that it is a charm syntax error. |
As charm code evolves, we delegate responsibilities to charm libs.
A common use case for charm libs is relation management.
A common pattern in relation management is for a lib to observe relation events on behalf of the charm, and "convert" them to tailored custom events.
When different relation data depend on each other, we encounter situations of stale data. For example, let's take a look at the following scenario:
Charm code misses out on the changes communicated over custom events because custom events do not result in charm re-init; they are processed already after
self.scrape
is init'ed with old data.To circumvent this, we have in place various approaches:
If custom events reinit'ed the charm, then code order concerns would probably be simpler.
Related: #736
The text was updated successfully, but these errors were encountered: