Month: June 2022

  • WWDC21 – ARC in Swift: Basics and beyond

    WWDC21 – ARC in Swift: Basics and beyond

    ARC in Swift: Basics and beyond

    Trong Swift thì chúng ta nên sử dụng value type như là struct hay là enum.
    Tuy nhiên, có một số trường hợp bắt buộc phải dùng reference type như là class thì chúng ta phải hết sức cẩn thận khi sử dụng để tránh reference cycle.

    Reference type trong Swift được quản lý bộ nhớ thông qua ARC (Autoatic Reference Counting).
    Trong bài viết này, chúng ta sẽ cùng tìm hiểu ARC làm việc như nào trong các version sắp tới, và một vài điểm chú ý khi sử dụng reference type.

    Object’s lifetimes

    An object’s lifetime in Swift begins at initialization and ends at last use.
    ARC automatically manages memory, by deallocating an object after its lifetime ends.
    It determines an object’s lifetime by keeping track of its reference counts.
    ARC is mainly driven by the Swift compiler which inserts retain and release operations.
    At runtime, retain increments the reference count and release decrements it.
    When the reference count drops to zero, the object will be deallocated.
    

    Từ trước đến giờ chúng ta vẫn luôn hiểu rằng ARC sẽ release một vùng nhớ khi không còn con trỏ nào trỏ vào nó. Ví dụ những vùng nhớ được khai báo trong block code, như function, sẽ được release sau khi content của function này kết thúc.

    Tuy nhiên, trên thực tế, mọi thứ còn hơn thế. Trong một function thì Object’s life time được tính từ khi nó được khởi tạo cho đến lần cuối cùng nó được dùng. Sau đó, lifetimes của nó sẽ kết thúc, và nó sẽ được xóa đi.

    Ví dụ, với những function giả sử dài 100 LOC, và chúng ta có object myObject nào đó được sử dụng trong khoảng 30 LOC đầu, thì sau khi lần cuối cùng nó được dùng, nó sẽ bị release đi, tức là trong khoảng thời gian 70 LOC còn lại được thực thi, thì vùng nhớ của myObject đã không còn tồn tại.

    Tham khảo đoạn source code bên dưới:

    class Traveller {
        var name: String
        var destination: String?
        
        init(name: String) {
            self.name = name
        }
    }
    
    func test() {
        let traveller1 = Traveller(name: "NhatHM") // traveller1 Object lifetime begins
        let traveller2 = traveller1
        traveller2.destination = "WWDC21-10216" // traveller1 Object lifetime ends
        print("Done travelling \(traveller2.destination)")
    }
    

    Ở ví dụ này, ngay sau khi object traveller1 được gán cho traveller2 thì nó đã không còn được sử dụng nữa, cho nên, đó cũng chính là thời điểm kết thúc lifetime của object traveller. Và lúc này, traveller1 đã sẵn sàng để bị release.

    Việc release object traveller1 là do Swift compiler tự xử lý, và thời điểm release là do ARC thực hiện.

    Như hiện tại, với XCode13 thì đã có option để optimize object life time. Tức là, có thể những bug trước đây chưa xảy ra vì coding logic observer đến object life time, thì bây giờ hoàn toàn có thể xảy ra. Bản thân Apple cũng khuyến cáo là khi coding, tránh các logic mà phụ thuộc vào object life time quá nhiều, vì với mỗi version Swift mới thì Swift compiler và ARC optimization có thể thay đổi, dẫn đến những bug tiềm ẩn có thể xảy ra.

    Note: ở đa số các ngôn ngữ lập trình khác, thì object chỉ bị release khi kết thúc body của function.

    Obserable object lifetimes và vấn đề của nó

    Thông qua:

    • weakunowned reference
    • Deinitializer (deinit)

    Dùng weak và unowned không sai, tuy nhiên trong một số trường hợp, nó rất có thể gây ra bug tiềm ẩn (phụ thuộc vào việc ARC được optimize như nào trong các phiên bản tiếp theo).

    Because relying on observed object lifetimes may work today, but it is only a coincidence.
    
    Observed object lifetimes are an emergent property of the Swift compiler and can change as implementation details change.
    

    Xem ví dụ dưới (captured from Apple WWDC21 video)

    Ở ví dụ trên, ngay sau đoạn code traveler.account = account kết thúc thì lifetimes của traveler đã kết thúc, và với các version Swift compiler sau này thì có thể xảy ra bug, vì lúc này, traveler bị release, do đó reference count đến vùng nhớ của Traveler đã bị release, dẫn đến đoạn code trong func printSummary sẽ bị crash, nếu dùng if let để unwrap value ra thì bản thân function này cũng sẽ bị sai logic, vì lúc này logic mong muốn print ra thông tin của traveler đã không còn được thực hiện.

    Đương nhiên, hiện tại code như trên sẽ chưa thể bug ngay được, vì Swift compiler đang chưa optimize đến mức là vừa kết thúc lifetimes của object thì object sẽ bị release đi luôn. Tuy nhiên, với sections này thì Apple đang nhắc nhở chúng ta vì việc tương lai, chắc chắn là việc xử lý memory của object lifetimes sẽ được thực hiện mạnh tay hơn, có thể ngay sau dòng code cuối cùng mà object được gọi thì nó sẽ bị release, và như vậy, source code của chúng ta chưa bug ở thời điểm này, nhưng tương lai có thể sẽ bị bug.

    How to fix

    Trong video, Apple gợi ý 3 cách fix:

    • Sử dụng withExtendedLifetime
    • Thay đổi solution code, sử dụng strong reference.
    • Thay đổi cách code, tránh sử dụng weak/unowned

    Dùng withExtendedLifetime (không khuyến khích)

    Sử dụng strong references

    Thay đổi cách code tránh các object reference lẫn nhau (khuyến khích)

    Kết luận:

    • Object lifetimes không tồn tại suốt vòng đời của function, mà chỉ tồn tại từ khi khởi tạo object -> lần cuối cùng object được sử dụng.
    • ARC optimization có thể thay đổi sau mỗi version của Swift (và Swift compiler), do đó, việc implement source code phụ thuộc quá nhiều vào object lifetimes tiềm ẩn bug.

    Nguồn:

  • Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 4

    Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 4

    Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 4

    Ở bài viết này, tôi sẽ giới thiệu một kỹ thuật gọi là decorator. Nhìn chung, nếu ai đã từng làm việc với các python web framework như Django, Flask, FastAPI,… thì sẽ sử dụng kỹ thuật này thường xuyên, nhưng không phải ai cũng hiểu rõ bản chất của nó.

    Thừa nhận rằng, khi sử dụng các decorators có sẵn mà các framework cung cấp, đã đủ để chúng ta làm việc như một web developer. Tuy nhiên, việc nắm được bản chất của kỹ thuật này cũng giúp cho chúng ta sử dụng decorators hiệu quả hơn, có thể tùy chỉnh và tạo ra các decorators của riêng mình, như vậy là ta đã trở thành một lập trình viên chuyên nghiệp hơn.

    Table of contents

    Decorators

    Nhắc lại một ví dụ trong Phần 3, chúng ta đã áp dụng Closure để maintain một bộ đếm – đếm số lần gọi một hàm bất kỳ.

    def counter(fn):
        cnt = 0  # số lần chạy fn, khởi tạo là 0
    
        def inner(*args, **kwargs):
            nonlocal cnt
            cnt = cnt + 1
            print('Hàm {0} đã được gọi {1} lần'.format(fn.__name__, cnt))
            return fn(*args, **kwargs)
    
        return inner

    Hàm counter nhận một function làm đầu vào – fn. Trong hàm counter, ta khởi tạo 1 biến cục bộ cnt – biến này sẽ đến số lần gọi hàm fn, mỗi khi goị đến hàm inner thì biến cnt được tăng lên 1 đơn vị.

    Việc sử dụng *args**kwargs trong hàm inner giúp ta có thể gọi hàm fn với bất kỳ sự kết hợp positional argumentskeyword-only arguments nào. Lấy ví dụ:

    def mult(x, y=10):
        """
        return the products of two values
        """
        return x * y
    
    mult = counter(mult) # trả về inner function - closure

    Nhớ rằng, ban đầu mult là một label trỏ đến hàm mult được định nghĩa ở trên. Goị hàm counter(mult) sẽ trả về một closure và gán vào một label là mult, thì lúc này mult sẽ trỏ đến closure đó, rõ ràng là khác so với nơi mà mult ban đầu trỏ đến.

    Tuy nhiên, inner thực hiện gọi hàm mult ban đầu cho chúng ta và trả về kết quả của nó, chính vì vậy mà kết quả trả về khi gọi hàm inner không khác so với việc gọi hàm mult ban đầu. Nhưng thực sự, hàm inner đã làm thêm một số việc trước khi gọi và trả về kết quả của hàm mult, đó là đếm số lần hàm mult được gọi.

    mult(3, 5)
    # In ra: Hàm mult đã được gọi 1 lần
    # return 15

    Chúng ta đã thực sự sửa đổi hàm mult ban đầu, bằng cách wrapping nó bên trong một hàm khác – bổ sung thêm chức năng cho nó –> chúng ta đã decorated hàm mult với hàm counter và chúng ta gọi counterdecorator function.

    Nhìn chung, một decorator function sẽ:

    • lấy 1 function như một đối số
    • return một closure
    • closure nói trên sẽ nhận bất kỳ sự kết hợp đầu vào nào (sử dụng *args và **kwargs)
    • chạy một vài chức năng bên trong closure
    • closure function sẽ gọi original function sử dụng các đối số được truyền vào closure
    • return kết quả được trả về từ function call nói trên

    The @ Symbol

    Như đã thấy, chúng ta hoàn toàn có thể sử dụng decorator function như sau:

    def my_func(*args, **kwargs):
        # some code here
        # ...
    my_func = func(my_func)

    Trong đó, funcdecorator function, còn my_func là hàm được decorated.

    Tuy nhiên, có một cách khác thanh lịch hơn:

    @func
    def my_func(*args, **kwargs):
        # some code here
        # ...

    Introspection

    Sử dụng lại counter như một decorator cho hàm mult:

    import inspect
    
    @counter
    def mult(x, y=10):
        """
        return the products of two values
        """
        return x * y
    
    print(f'name = {mult.__name__}') # name = inner

    Ta thấy rằng, câu lệnh print ở trên sẽ in ra màn hình name = inner. Rõ ràng, tên của hàm mult không phải là mult nữa, mà nó là inner – tên của một closure, điều này chứng tỏ một điều rằng, hàm mult lúc này chính là một closure.

    Vì vậy, cầu lưu ý rằng, khi chúng ta decorate một function, tức là chúng ta đã làm cho function đó thay đổi (về bản chất).

    Để chắc chắn hơn, hãy thử gọi:

    print(f'signature: {inspect.signature(mult)}') # signature: (*args, **kwargs)

    Như vậy, khi gọi inspect.signature(mult), chúng ta đã nhìn thấy rõ chữ ký của hàm inner được trả về.

    Việc decorate một function làm thay đổi docstring, signature, name khiến cho việc debugging trở nên khó khăn hơn rất nhiều.

    The wraps function

    Để giải quyết vấn đề mới nói ở trên, functools module cung cấp một wraps function có thể fix metadata của inner function trong decorator. Bản thân wraps function này là một decorator.

    Hàm wraps này sẽ decorate inner function để thay đổi metadata (docstring, signature, name,…) của nó. Thêm nữa, nó phải sử dụng hàm mult như một đối số đầu vào, để có thể thay thế metadata của inner function bằng metadata của mult function (hàm được decorated)

    from functools import wraps
    import inspect
    
    def counter(fn):
        cnt = 0  # số lần chạy fn, khởi tạo là 0
    
        @wraps(fn)
        def inner(*args, **kwargs):
            nonlocal cnt
            cnt = cnt + 1
            print('Hàm {0} đã được gọi {1} lần'.format(fn.__name__, cnt))
            return fn(*args, **kwargs)
        return inner
    
    @counter
    def mult(x, y=10):
        """
        return the products of two values
        """
        return x * y
    
    print(f'name = {mult.__name__}') # name = mult
    print(f'signature: {inspect.signature(mult)}') # signature: (x, y=10)

    Chúng ta không nhất thiết phải sử dụng wraps function trong khi sử dụng decorator, nhưng việc sử dụng nó sẽ giúp cho việc debugging trở nên dễ dàng hơn.

    Summary

    1. Decorators trong Python là cú pháp cho phép một function sửa đổi một function khác tại thời gian chạy (runtime)
    2. Ký pháp @ giúp cho việc sử dụng decorator thanh lịch hơn (mặc dù có thể không cần)
    3. Hãy sử dụng wraps function giúp cho việc debugging dễ dàng hơn khi làm việc với decorators.

    References

    [1] Fred Baptiste, Python Deep Dive, Part 1

    [2] Brett Slatkin, Effective Python, Item 26

    Authors

    [email protected]

  • Gitlab CI/CD cho người mới bắt đầu – Phần 2: Configure Runner

    Gitlab CI/CD cho người mới bắt đầu – Phần 2: Configure Runner

    Xin chào mọi người,
    Chúng ta đã quá lâu không gặp lại nhau rồi đúng không?
    Như đã nói từ kì trước, để tiếp tục series "Gitlab CI/CD cho người mới bắt đầu", thì ở bài này mình sẽ viết về cách set-up local gitlab runner, cụ thể hơn, chúng ta sẽ setting 1 runner qua Docker.

    Tại sao lại là Docker chứ không phải là trên Window hay MacOS? Mình chọn Docker vì nó đơn giản với những người mới, cộng với tính linh động của nó trên nhiều môi trường khác nhau. Vì vậy, nên để có thể tiếp tục với tutorial này thì việc đầu tiên bạn cần làm là có docker ở trên máy của mình. (Bạn có thể xem hướng dẫn tải và cài đặt tại đây) Nếu bạn đã cài đặt thành công Docker trên máy, thì chúng ta sẽ bắt đầu vào phần chính ngay thôi.

    Về cơ bản thì việc khởi tạo và chạy runner qua Docker này sẽ trải qua 2 bước: (Chi tiết có thể xem tại đây)

    Bước 1: Khởi tạo và chạy gitlab runner

    https://docs.gitlab.com/runner/install/  
    docker run -d --name gitlab-runner --restart always \
      -v gitlab-runner:/etc/gitlab-runner \
      -v /var/run/docker.sock:/var/run/docker.sock \
      gitlab/gitlab-runner:latest 
    

    Bước 2: Đăng kí gitlab runner đó với project mà chúng ta muốn sử dụng

    docker exec -it gitlab-runner bash 
    
    gitlab-runner register -n  --name GitLabRunner  \
    --executor docker  --docker-image docker:latest  \
    --docker-volumes /var/run/docker.sock:/var/run/docker.sock  \
    --url ${GITLAB_URL}  \
    --registration-token ${GITLAB_TOKEN} \
    --tag-list cicd_tag
    

    Ở bước này chúng ta sẽ cần config 2 biến env trong phần variable tại repo gitlab,

    • Biến thứ nhất GITLAB_URL chính là URL tới repo cần config runners,
    • Biến thứ 2 GITLAB_TOKEN là secret token để runner kết nối với repo trên gitlab. Gitlab Token & Gitlab Url

    (Ngoài ra các bạn có thế xem thêm các cài đặt nâng cao khi đăng kí runner tại đây)

    Khi đăng kí runner như trên, bạn sẽ cần chú ý vào tag-list, tại đây thì runner sẽ nhận diện những job nào được gắn tag đó để khởi chạy!

    Đó, và như vậy là chúng ta đã đăng kí thành công local runner cho repo gitlab. Thật đơn giản phải không? =)))

    Stay tuned and keep waiting for our next articles nhớ <3.
    Xin chào thân ái và quyết thắng tới mn!