How do I reconnect or replace a resource my object holds?

← Error Handling and Exceptions · Ref: Q1841

You do not re-point it: a closeable property is set ONCE, in its own initialiser or its own type's constructor, while it holds nothing yet, and its type's operator close releases it when the object is closed. Any other assignment - ':=' in a reconnect() method, an assignment through another object, a constructor setting it a second time - is error E81063, because it drops the resource the property held and nothing would ever close it. The guarded ':=?' is no escape: a connection that failed or was closed is present but UNSET, so ':=?' replaces it and still drops it.

When code needs a new resource for each attempt, acquire it in a try where it is used - a fresh connection per try. Each attempt's connection is closed when its try ends, whether the attempt worked or not. When an object needs a different resource for its whole life, make a new object: its constructor acquires the resource and its operator close releases it.

The program below retries a send three times with a fresh connection per try; the first two attempts fail, and every connection is closed exactly once.

Example

defines module qa.errorhandling.replaceaheldresource

  defines class

    Connection
      name as String?
      healthy as Boolean?

      default private Connection()

      Connection()
        ->
          text as String
          works as Boolean
        name: text
        healthy: works

      label() as pure
        <- rtn as String: name

      send()
        -> message as String
        <- rtn as Boolean: healthy
        stdout <- Stdout()
        if healthy
          stdout.println(`sent ${message} on ${name}`)
        else
          stdout.println(`${name} failed`)

      operator close as pure
        stdout <- Stdout()
        stdout.println(`closed ${name}`)

      default operator ?

    <?- Its connection is set once, in the constructor, and closed with the session -?>
    Session
      conn as Connection?

      default private Session()

      Session()
        -> text as String
        conn: Connection(text, true)

      describe()
        stdout <- Stdout()
        stdout.println(`session on ${conn.label()}`)

      default operator close

      default operator ?

  defines program

    RetryWithFreshConnection()
      stdout <- Stdout()

      try
        -> session <- Session("primary")
        session.describe()

      maximumAttempts <- 3
      sent <- false
      attempt <- 1

      while not sent and attempt <= maximumAttempts
        try
          -> conn <- Connection(`attempt ${attempt}`, attempt == maximumAttempts)
          sent := conn.send("hello")
        attempt++

      stdout.println(`sent: ${sent}`)

Common mistakes

E81063 — A closeable property is set once, while its object is constructed. Re-pointing it in a method drops the connection it held and nothing closes it; acquire a fresh connection in a try instead. See ek9 -h E81063 for details.

Incorrect:

      describe()
        conn := Connection("replacement", true)
        stdout <- Stdout()
        stdout.println(`session on ${conn.label()}`)

Correct:

      describe()
        stdout <- Stdout()
        stdout.println(`session on ${conn.label()}`)
Other ways to ask this
  • Why is 'conn := Connection(..)' in a reconnect method an error?
  • Can I use ':=?' to open a connection only if it is not open?
  • How do I retry with a new connection in EK9?
  • What does E81063 mean?

Coming from another language?

Java: 'this.conn = connect()' in a reconnect method silently leaks the old socket unless you remember to close it first. C#: the same, and a disposable field re-assigned is a classic CA2213 miss. Python: 'self.conn = connect()' leaks until the garbage collector finalises it. EK9: re-pointing a closeable property is a compile error (E81063); acquire a fresh resource in a try per attempt.

Keywords: re-point, operator close, retry, close, E81063, property, reconnect, assign if unset, try-with-resources, closeable