Giter Club home page Giter Club logo

nimgo's Introduction

NimGo

NimGo: Asynchronous Library Inspired by Go's Asyncio. Or for Purists: Stackful Coroutines library associated with an I/O Event Loop and Dispatcher

This repository is currently an alpha release. Breaking change and API redesign won't be avoided.

Goal

Provide a simple, concise and efficient library for I/O.

No async/await, no pragma, no Future[T] everywhere !

Only one word to remember : goAsync (and optionaly wait, but seriously who needs that ?)

Current state

All working features can be found here.

For now, NimGo is a single-threaded library. On Araq's advice (and maybe it's help), NimGo will be transformed towards a multi-threaded library (like Golang). This specific transformation has its own roadmap that can be found here.

Documentation

Full documentation can be browsered here. The documentation is still under construction.

Contributions

They are welcomed and any help is valuable. A code of contribution can be found here.

Example

The following working example will give you an idea of how NimGo works and how to use it.

# Depending on your OS, the following example might not yet work
import nimgo, nimgo/gofile

let MyFilePath = currentSourcePath()

## # Basic I/O
proc readAndPrint(file: GoFile) =
  # readAndPrint will be suspended until file.readLine return
  echo "MYLINE=", file.readLine()
  # file.readAll will be registered in dispatcher. readAndPrint can continue its execution
  var readTask: GoTask[string] = goAsync file.readAll()
  # we decide finally to get its data
  # readAndPrint will be suspended until readTask is finished
  echo "UNREADLENGTH=", (wait readTask).len()

withEventLoop():
  var myFile = openGoFile(MyFilePath)
  goAndWait readAndPrint(myFile)
  echo "I'm not waiting for readAndPrint to finish !"
  echo "But `withEventLoop` ensures all registered tasks are executed"
  myFile.close()

## # Coroutines communication

## ## Returning a value:
block:
  proc getFirstLine(f: GoFile): string =
    f.readLine()
  var myFile = openGoFile(MyFilePath)
  echo "MYLINE=", goAndWait getFirstLine(myFile)
  myFile.close()

## ## With closures:
proc main() =
  # Any GC value can be shared between coroutines
  var sharedData: string
  ## We have to use wait, otherwise sharedData will not be updated yet
  goAndWait proc() =
    sharedData = "Inside the coroutine"
  echo sharedData
main()


## # Unordered execution

proc printInDesorder(sleepTimeMs: int) =
  sleepAsync(sleepTimeMs)
  echo "> I woke up after ", sleepTimeMs, "ms"

withEventLoop():
  echo "Batch 1"
  goAsync printInDesorder(200)
  goAsync printInDesorder(100)
  goAsync printInDesorder(50)
# Or using waitAll
echo "Batch 2"
waitAll @[
  goAsync printInDesorder(110),
  goAsync printInDesorder(220),
  goAsync printInDesorder(60),
]

## Timeout
goAndWait proc() =
  echo "Please input from stdin: "
  var data = goStdin.readChunk(timeoutMs = 500)
  if data.len() == 0:
    echo "> Too late"
  else:
    echo "> So fast, you have succesfully written: ", data

Here are the place wher eyou can find more examples:

Quicktour of the modules

Certainly! The NimGo library consists of the following key modules:

  • nimgo: This module provides the necessary tools to create and manage the flow of execution.
  • nimgo/gofile: This module offers all the asynchronous I/O operations for files and pipes.
  • nimgo/gostreams: This module provides an internal channel called GoBufferStream, as well as a common API for working with this channel and files.
  • nimgo/gonet: This module handles all the operations for working with sockets asynchronously.
  • nimgo/goproc: This module exposes an API for creating child processes, executing commands, and interacting with them asynchronously.
  • nimgo/coroutines: This module provides the low-level API for the stackful coroutines, which is abstracted away for most users.
  • nimgo/eventdispatcher: This module exposes the low-level API for using the event loop and dispatcher.
  • nimgo/public/gotasks: This module is already imported with the nimgo package and provides the GoTask abstraction for manipulating the flow of execution and return values.

Most users will primarily interact with the higher-level modules like nimgo, nimgo/gofile, nimgo/gonet, and nimgo/public/gotasks, while the lower-level modules (nimgo/coroutines and nimgo/eventdispatcher) are intended for more advanced use cases.

Miscelleanous

What do Araq thinks of it ?

"Looks nice and has good ideas. But what's the benefit of this over just using threads? Coorperative scheduling is bug-prone much like multi-threading is IMHO without its performance benefits." from Araq, creator of Nim language and its main developper, 07/06/2024

How does it work ?

If you are interested to know more about NimGo and Coroutines, you can check the wiki !

NimGo seems to have a high memory usage ?

NimGo coroutines use a lot of virtual memory, but not much actual physical memory (RAM).

Here's why:

  • NimGo gives each coroutine a large amount of virtual memory, just in case they need it.
  • But coroutines only use the physical memory they actually need.
  • The operating system only allocates physical memory pages as the coroutines use them.
  • So the high virtual memory usage doesn't mean high RAM usage. It's just a way to let the coroutines grow if they need to.

The virtual memory usage may look high, but the actual RAM usage is much lower. This design doesn't prevent other programs from running. You can see the real memory usage by NimGo by looking at RESIDENT (RES) memory in the top command.

nimgo's People

Contributors

alogani avatar

Stargazers

Jakub Pieńkowski avatar  avatar Hoang Phan avatar orzogc avatar  avatar Huy Doan avatar  avatar  avatar  avatar Dinesh Chander avatar  avatar José Paulo avatar  avatar  avatar Regis Caillaud avatar George Lemon avatar Trayambak Rai avatar Neko Hz avatar  avatar  avatar Andreas Rumpf avatar Pietro Peterlongo avatar Bung avatar Scott Wadden avatar Gabben avatar  avatar  avatar O God please eliminate Israel avatar Aleksey avatar  avatar  avatar Yevhen Ts. avatar Antonis Geralis avatar  avatar Armando Hinojosa avatar  avatar

Watchers

O God please eliminate Israel avatar  avatar  avatar

Forkers

araq hamidb80

nimgo's Issues

Prevent stackoverflow

Handling stack memory has multiple aspects into achieving a reliable, secure, and explicit error management.

Growable stack

This might not be easy to implement. So the best solution is certainly to rely on exclusively on having a very big stack of virtual memory. There is still the problematic to support lower end device, so we shall determine the maximum amount of storage to allocate (for example sysconf(SC_PAGESIZE) * (sysconf(SC_PHYS_PAGES) on linux) and to support operating systems that can't support virtual memory (which ones ?)

Protection against overflow

The last allocated page for the stack should be marked as protected:

  • m_protect on linux
  • VirtualProtect on windows

Error message in case of stackoverflow

When a page of memory is protected, the OS will throw an exception like SIGESEGV on linux. This can be catched but cannot be recovered safely (at least on linux). So SIGSEGV shall be catched to permit to write the stacktrace, the error message. However, SIGEGV from stackoverflow should be distinguished from other SIGSEGV (how ?)

Some resources:

Rename goAsync keyword

Because the library won't be strictly async, the keyword named `goAsync might not be appropriate ?

Here are some propositions:

  • go
  • goTask
  • goAsync
  • goNim
  • launch
  • startTask
  • coSpawn

Testing if NimGo test suite pass in windows

If you have time, don't hesitate to run the test suites and the bench on windows with NimGo and tell me what are the errors messages.

If you know how and it is possible, you can also tell me how to add a windows github hosted runner

Any proposition of fix are also welcome ;-)

Redesign the design of the selector pool to make it work in windows.

This is the last major work before work on multithreading (#21) could begin.

The goal is to create a portable API to query asynchronous events. This will likely significantly redesign the public API of the nimgo/eventdispatcher library, and hopefully the API won't require many changes after this update.

Completing goproc module

  • defining the API
  • Writing startProcess for posix
  • Handle the pseudo terminal specific case for posix
  • Add convenience function run
  • Implements the window's low level API (help wanted)
  • etc?

Changing how Coroutine's internal struct is allocated

Coroutine ref object in fact wraps a McoCoroutine struct defined in minocoro.h This struct holds low level information of the coroutine. It uses calloc/free. It should be allocated instead on Nim's side with Nim's alloc/dealloc

Rename the NimGo library

A user in the Nim Forum suggests to rename NimGo for a better name. I think he might be right.

Here is his post https://forum.nim-lang.org/t/11720#75214

I think NimGoroutines shall be a good name. It expresses both the concept of Coroutines, the inspiration of Go in that specific feature (Goroutines), and is not restricted to only IO (because NimGo will also leverage Os Threads in future release).

Reuse raw protected memory (in remplacement of coroutinepool implementation)

On posix: Deallocation of virtual memory allocated with mmap by a factor >10 slower than virtual memory allocated with alloc. But memory allocated with alloc is incompatible with mprotect

proc mcoDeallocator(p: pointer, size: uint, allocatorData: ptr CoroutineObj) {.cdecl.} =
when NimGoNoVMem:
dealloc(p)
else:
if munmap(p, size.int) != 0:
raiseOSError(osLastError())
when not NimGoNoDebug:
unrecordProtectedPage(allocatorData)
proc mcoAllocator(size: uint, allocatorData: ptr CoroutineObj): pointer {.cdecl.} =
when NimGoNoVMem:
result = alloc0(size)
else:
result = mmap(nil, size.int,
bitor(PROT_READ, PROT_WRITE),
bitor(MAP_PRIVATE, MAP_ANONYMOUS),
-1, 0
)
if result == MAP_FAILED:
raiseOSError(osLastError())
if mprotect(result, PageSize, PROT_NONE) != 0:
## Stack begins at its bottom
mcoDeallocator(result, size, nil)
raiseOSError(osLastError())
when not NimGoNoDebug:
recordProtectedPage(allocatorData, result)

@Araq Do you have some knowledge of those memory internals that could explain why alloc is so much faster but incompatible with PROT_NONE pages ? That order of magnitude is not negligible

Convert 4-space indent to 2-space indent

Although I highly prefer four spaces ident, this is the norm in Nim and it will facilitate contributions. I have modified my VsCodium settings to help me see them better.

Open reflexions to implement a M:N multi threaded dispatcher on NimGo

Please read here the definitions I use before responding to this post. If we don't agree on what we talk, we won't talk about much.

Questions

  1. Should the EventLoop and DispatcherLoop be merged together ?
  2. If they are separate, should the EventLoop run in its own thread or each thread would run a shared EventLoop ?
  3. Should each thread have its own task Dispatcher in addition to global task Dispatcher ?
  4. Should it have a fixed number of Threads ? If yes, how many ? If no, determined by what ?
  5. What design should the DispatcherLoop have ?
  • FIFO for all tasks, except LIFO for last task for better caching/responsivness ?
  • FIFO only ?
  1. Other considerations ?

Proposition of response

If I read correctly this paper shared from @mratsim : https://assets.ctfassets.net/oxjq45e8ilak/48lwQdnyDJr2O64KUsUB5V/5d8343da0119045c4b26eb65a83e786f/100545_516729073_DMITRII_VIUKOV_Go_scheduler_Implementing_language_with_lightweight_concurrency.pdf

The answers could be :

  1. No
  2. Separate thread for I/O pool
  3. Yes
  4. Should have 61 Os Threads (why ?)
  5. FIFO with LIFO for last item

However I am wondering how well a single selector can scale. But having multiple selectors by thread is trickier, even if the same fd can be registered in multiple dispatcher (with special considerations for thread safety, but this might be implementation specific).

gostdin/gostdout/gostederr aren't registered correctly

import nimgo, nimgo/gofile

var file = currentSourcePath()
var gf = openGoFile(file)

withEventLoop():
    goAsync proc() =
        echo gostdin.readline()

src/nimgo/eventdispatcher.nim(424) addInsideSelector
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.