Skip to content
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

cloud.Api handler (AWS Lambda) - "Warm start" #5440

Closed
meirdev opened this issue Jan 14, 2024 · 9 comments
Closed

cloud.Api handler (AWS Lambda) - "Warm start" #5440

meirdev opened this issue Jan 14, 2024 · 9 comments
Labels
🐛 bug Something isn't working

Comments

@meirdev
Copy link
Contributor

meirdev commented Jan 14, 2024

I tried this:

Edit: A better example

bring cloud;

class Database {
    new() {
    }

    pub inflight query(sql: str): Array<Json> {
    }
}

let db = new Database();

let api = new cloud.Api();

api.get("/users", inflight (req) => {
    let users = db.query("select * from users");

    return {
        status: 200,
        body: Json.stringify(users),
    };
});

This happened:

The generated code looks like this:

"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};

// ../../../../../../../opt/homebrew/lib/node_modules/winglang/node_modules/@winglang/sdk/package.json
var require_package = __commonJS({
....
});

// inflight.Database-1.js
var require_inflight_Database_1 = __commonJS({
  "inflight.Database-1.js"(exports2, module2) {
    "use strict";
    module2.exports = function({}) {
      class Database {
        static {
          __name(this, "Database");
        }
        constructor({}) {
        }
        async query(sql) {
        }
      }
      return Database;
    };
  }
});

// get_users_}0_c81e9ef5.js
exports.handler = async function(event) {
  return await new (require_api_onrequest_inflight()).ApiOnRequestHandlerClient({
    handler: await (async () => {
      const $Closure1Client = require_inflight_Closure1_1()({
        $db: await (async () => {
          const DatabaseClient = require_inflight_Database_1()({});
          const client2 = new DatabaseClient({});
          if (client2.$inflight_init) {
            await client2.$inflight_init();
          }
          return client2;
        })(),
        $std_Json: require_json().Json
      });
      const client = new $Closure1Client({});
      if (client.$inflight_init) {
        await client.$inflight_init();
      }
      return client;
    })(),
    args: {}
  }).handle(event);
};

I expected this:

No response

Is there a workaround?

No response

Anything else?

Is it possible to insert code that will run outside the handler function?

I want to take advantage of the warm start of the lambda in situations such as: connecting to a database, saving cache, etc.

Wing Version

0.54.30

Node.js Version

v18.16.0

Platform(s)

MacOS

Community Notes

  • Please vote by adding a 👍 reaction to the issue to help us prioritize.
  • If you are interested to work on this issue, please leave a comment.
@meirdev meirdev added the 🐛 bug Something isn't working label Jan 14, 2024
@monadabot monadabot added this to Wing Jan 14, 2024
@github-project-automation github-project-automation bot moved this to 🆕 New - not properly defined in Wing Jan 14, 2024
@eladb
Copy link
Contributor

eladb commented Jan 15, 2024

You can use inflight new() for this:

bring cloud;

class MyType {
  cache: cloud.Bucket;

  new(cache: cloud.Bucket) {
    this.cache = cache;
  }

  inflight cachedValue: str;

  inflight new() {
    log("load from cache");
    this.cachedValue = this.cache.get("value");
  }

  pub inflight doStuff() {
    log("hi");
    log(this.cachedValue);
  }
}

let b = new cloud.Bucket();
b.addObject("value", "hello");

let t = new MyType(b);

new cloud.Function(inflight () => {
  for i in 0..1000 {
    t.doStuff();
  }
});

This will print:

load from cache
hello
hello
hello
...x100

Let me know if this is what you were after.

@meirdev
Copy link
Contributor Author

meirdev commented Jan 15, 2024

Not really... the generated code looks like this:

// cloud.function_c8d2eca1.js
exports.handler = async function(event) {
  return await (await (async () => {
    const $Closure1Client = require_inflight_Closure1_1()({
      $t: await (async () => {
        const MyTypeClient = require_inflight_MyType_1()({});
        const client2 = new MyTypeClient({
          $this_cache: new (require_bucket_inflight()).BucketClient(process.env["BUCKET_NAME_d755b447"])
        });
        if (client2.$inflight_init) {
          await client2.$inflight_init();
        }
        return client2;
      })()
    });
    const client = new $Closure1Client({});
    if (client.$inflight_init) {
      await client.$inflight_init();
    }
    return client;
  })()).handle(event);
};

If you run the two codes in AWS:

class Counter {
  constructor() {
    this.counter = 0;
  }

  inc(n) {
    this.counter += n ?? 1;
  }

  dec(n) {
    this.counter -= n ?? 1;
  }

  set(n) {
    this.counter = n;
  }

  peek() {
    return this.counter;
  }
}

const counter = new Counter();

export const handler = async (event) => {
  counter.inc();

  return {
    statusCode: 200,
    body: `${counter}`,
  };
};
bring cloud;

class Counter {
    inflight var counter: num;

    inflight new() {
        this.counter = 0;
    }

    pub inflight inc(n: num?) {
        this.counter += n ?? 1;
    }

    pub inflight dec(n: num?) {
        this.counter -= n ?? 1;
    }

    pub inflight set(n: num) {
        this.counter = n;
    }

    pub inflight peek(): num {
        return this.counter;
    }
}

let counter = new Counter();

new cloud.Function(inflight () => {
    counter.inc();

    return Json.stringify({
        status: 200,
        body: "{counter.peek()}",
    });
});

You will see that the counter continues to increase in the JS code with each request (until, of course, the virtual machine is shutdown).

The more important case for me is of course the connection to a database which can take a relatively long time.

@eladb
Copy link
Contributor

eladb commented Jan 16, 2024

You are right. The client initialization should happen only once and not in every call to handle.

@yoav-steinberg @Chriscbr curious if you have any thoughts here

@yoav-steinberg
Copy link
Contributor

Right currently the lifting code (which calls the inflight new) runs on each invocation. This is logically sound but doesn't take "warm start" optimizations into effect. To get this right we need to move the lifting code (and the inflight new execution) to the global level of the generated js function.
Lets mark this down to be done in the future. Can't think of a quick fix for now.

@eladb
Copy link
Contributor

eladb commented Jan 16, 2024

Perhaps as an alternative to hoisting the lifting code to the global level we can implement a global cache object so that clients are only created once.

@yoav-steinberg
Copy link
Contributor

Perhaps as an alternative to hoisting the lifting code to the global level we can implement a global cache object so that clients are only created once.

So the global cache is always created in the global scope, and each lifted client is looked up there before being instantiated?

@eladb
Copy link
Contributor

eladb commented Jan 16, 2024

each lifted client is looked up there before being instantiated

exactly

@Chriscbr
Copy link
Contributor

It sounds like this is

@github-project-automation github-project-automation bot moved this from 🆕 New - not properly defined to ✅ Done in Wing Jan 16, 2024
@Chriscbr
Copy link
Contributor

It sounds like we can collapse this with #3244

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working
Projects
Archived in project
Development

No branches or pull requests

4 participants