דילוג לתוכן
  • דף הבית
  • קטגוריות
  • פוסטים אחרונים
  • משתמשים
  • חיפוש
  • חוקי הפורום
כיווץ
תחומים

תחומים - פורום חרדי מקצועי

💡 רוצה לזכור קריאת שמע בזמן? לחץ כאן!
  1. דף הבית
  2. תכנות
  3. קריאה אסינכרונית משרת HTTP בGO

קריאה אסינכרונית משרת HTTP בGO

מתוזמן נעוץ נעול הועבר תכנות
43 פוסטים 4 כותבים 788 צפיות
  • מהישן לחדש
  • מהחדש לישן
  • הכי הרבה הצבעות
התחברו כדי לפרסם תגובה
נושא זה נמחק. רק משתמשים עם הרשאות מתאימות יוכלו לצפות בו.
  • dovidD מנותק
    dovidD מנותק
    dovid ניהול
    כתב ב נערך לאחרונה על ידי dovid
    #6

    הצלחתי לשחזר את הבאג.
    הפייבר עושה שימוש באותו ctx, כנראה עם נעילה (mutex וכדומה) כך שברור שאין שימוש בקודם. הבעיה שההשהיה מנטרלת את הנעילה, כלומר הפוקנציה באמת נגמרה (והיא לא ביצעה עוד את השורה המושהית) ופייבר ממשיך לבקשה הבאה.
    לגבי העתקת הערך לפונקציה שלכאורה "מנתקת" את הערך ע"י "העתקתו", אז הנה למדנו משהו על go. אלא שאני לא מצליח "ללמוד" פה, ואשמח להסבר על זה.
    (בדוגמה שלי להלן יש המחשה לבעיה אבל שמה גם ההדפסה הראשונה שגויה בגלל שאין נעילה בכלל גם בהתחלה, משא"כ בבקשות הhttp של פייבר שיש נעילה עד לסיום הפונקציה המטפלת של הget. משא"כ ההדפסה הראשונה משוחררת מנעילה ומבוצעת מיידית).

    https://play.golang.org/p/OLboA4D9qng

    מה שקורה פה זה עם לולאה, מעבירים מצביע למבנה לפונקציה, שהיא לכאורה מנתקת את הערך ע"י השמה למשתנה אבל זה לא עוזר.

    מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

    בכל נושא אפשר ליצור קשר dovid@tchumim.com

    nigunN תגובה 1 תגובה אחרונה
    3
    • dovidD מנותק
      dovidD מנותק
      dovid ניהול
      כתב ב נערך לאחרונה על ידי dovid
      #7

      טעיתי. אני כעת רואה שgorutine מתחילות לפעול רק אחרי סיום הקוד הנוכחי, כלומר הbridge(&p) ייקרא עשרה פעמים רק אחרי השינוי האחרון של המאפיין name כך שהתוצאה הגיונית לגמרי.

      מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

      בכל נושא אפשר ליצור קשר dovid@tchumim.com

      yossizY תגובה 1 תגובה אחרונה
      3
      • nigunN מנותק
        nigunN מנותק
        nigun
        השיב לyossiz ב נערך לאחרונה על ידי
        #8

        @yossiz אמר בקריאה אסינכרונית משרת HTTP בGO:

        @nigun אגב, פרוייקט פייבר נראה מאוד צעיר ואולי לא מוכן לפרודקשן

        אתה צודק זה פרוייקט בן חודשיים סך הכל
        כנראה הם ישמחו אם אני ידווח להם על הבאג
        אבל אני לא יודע לנסח את הכותרת
        אתה יכול עזור לי בזה קצת? (לפחות לפתיח את ההמשך אני יכול לכתוב לבד)

        מייל: nigun@duck.com

        תגובה 1 תגובה אחרונה
        0
        • yossizY מנותק
          yossizY מנותק
          yossiz
          השיב לdovid ב נערך לאחרונה על ידי
          #9

          @dovid לא הבנתי איפה אתה אוחז אחרי הפלפול
          לכאורה המקרה של @nigun לא דומה לדוגמה שלך, כי אצלך המשתנה לא מקבל ערך אחר באמצע ריצת הפונקציה.
          ואם כן תמוה איך בקוד של @nigun המחרוזת (שהוא value type, לא?) משתנה באמצע ריצה.

          📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

          dovidD תגובה 1 תגובה אחרונה
          2
          • nigunN מנותק
            nigunN מנותק
            nigun
            השיב לdovid ב נערך לאחרונה על ידי
            #10

            @dovid
            קודם כל אני מופתע שאתה מונח טוב בGO (מספיק בשביל לדבג)
            בדקתי עכשיו מה קורה אם אני משתמש בשרת fasthttp שעליו פיבר מבוסס (הוא גם משתמש בctx)
            ושם התוצאה תקינה
            כנראה שהבעיה היא בפיבר

            
            package main
            
            import (
                    "flag"
                    "fmt"
                    "log"
                    "time"
            
                    "github.com/valyala/fasthttp"
            )
            
            var (
                    addr     = flag.String("addr", ":3000", "TCP address to listen to")
                    compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
            )
            
            func main() {
                    flag.Parse()
            
                    h := requestHandler
                    if *compress {
                            h = fasthttp.CompressHandler(h)
                    }
            
                    if err := fasthttp.ListenAndServe(*addr, h); err != nil {
                            log.Fatalf("Error in ListenAndServe: %s", err)
                    }
            }
            
            func requestHandler(ctx *fasthttp.RequestCtx) {
                    go myfunc(string(ctx.Path()))
                    fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
            }
            func myfunc(number string) {
                    fmt.Printf("number is %s \n", number)
                    time.Sleep(1 * time.Second)
                    fmt.Printf("number is now %s \n", number)
            }
            
            

            מייל: nigun@duck.com

            י yossizY 2 תגובות תגובה אחרונה
            0
            • dovidD מנותק
              dovidD מנותק
              dovid ניהול
              השיב לyossiz ב נערך לאחרונה על ידי dovid
              #11

              @yossiz אתה צודק, זה קושיא על go, רק שלא הצלחתי לשחזר את זה (בלי פיבר).

              מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

              בכל נושא אפשר ליצור קשר dovid@tchumim.com

              תגובה 1 תגובה אחרונה
              0
              • י מנותק
                י מנותק
                יוסף בן שמעון
                השיב לnigun ב נערך לאחרונה על ידי
                #12
                פוסט זה נמחק!
                תגובה 1 תגובה אחרונה
                5
                • yossizY מנותק
                  yossizY מנותק
                  yossiz
                  כתב ב נערך לאחרונה על ידי yossiz
                  #13

                  מצאתי את מקור הבעיה
                  https://github.com/gofiber/fiber/blob/efe89d243de6d8cf964581f5832b4341e2baa427/utils.go#L128-L130
                  הפונקציה הנ"ל בשימוש רחב בקובץ context.go
                  וזה הרי כבר מתועד בתיעוד הרשמי של fasthttp שאסור להשתמש ב-ctx אחרי שה-handler חוזר. וכן בהערה כאן, ועיין עוד: https://github.com/valyala/fasthttp/issues/146

                  אם fiber לא היו מחפים על השימוש ב-byte array על ידי המרה בצורה לא בטוחה למחרוזת היה יותר קל למצוא את הבעיה.

                  @dovid אמר בקריאה אסינכרונית משרת HTTP בGO:

                  אז הנה למדנו משהו על go

                  לפי דרכי למדנו שלא כדאי להשתמש ב-unsafe בלי להבין טוב טוב את ההשלכות... (דבר פשוט מאוד...)

                  📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                  dovidD תגובה 1 תגובה אחרונה
                  2
                  • yossizY מנותק
                    yossizY מנותק
                    yossiz
                    כתב ב נערך לאחרונה על ידי
                    #14

                    @nigun
                    https://github.com/gofiber/fiber/issues/185

                    📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                    תגובה 1 תגובה אחרונה
                    1
                    • dovidD מנותק
                      dovidD מנותק
                      dovid ניהול
                      השיב לyossiz ב נערך לאחרונה על ידי
                      #15

                      @yossiz ענית רק על השימוש שדורס את המיקום המדוייק בזיכרון.
                      אבל למה המשתנה החדש מושפע ממיקום הבתים של הטיפוס?

                      מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

                      בכל נושא אפשר ליצור קשר dovid@tchumim.com

                      yossizY תגובה 1 תגובה אחרונה
                      0
                      • yossizY מנותק
                        yossizY מנותק
                        yossiz
                        השיב לdovid ב נערך לאחרונה על ידי yossiz
                        #16

                        @dovid אם הבנתי נכון מה שאתה שואל אז התשובה היא שמשתנה מסוג string ב-GO לא מכיל את הבייטים של המחרוזת, הוא רק מצביע למקום בזכרון. לכן על אף שלכל קריאה ל-myfunc מיוצר string חדש, אבל כולם מצביעים על אותו איזור בזיכרון.
                        עיין כאן: https://www.reddit.com/r/golang/comments/57ica9

                        📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                        dovidD תגובה 1 תגובה אחרונה
                        0
                        • dovidD מנותק
                          dovidD מנותק
                          dovid ניהול
                          השיב לyossiz ב נערך לאחרונה על ידי dovid
                          #17

                          @yossiz אני שואל איך יכול להיות שהnumber נהרס ע"י גישה ישירה לזיכרון לtype של הctx.
                          הרי הוא עותק. שהרי כשאתה משים משתנה מחרוזת למשתנה אחר זה כן כתובת שונה ותוכן בלתי תלוי, אז מה זה שונה.
                          בכל מקרה גם בשימוש בunsafe לא מתקבל על הדעת שיהיה דרך בשפה עילית מודרנית לשנות ערכים של זיכרון שלא באחריות הקוד שלי, אני ממש לא מבין איך זה ייתכן.
                          אני מבין שאני מפספס משהו, מה קורה כעושים number:= ctx.XXX.

                          מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

                          בכל נושא אפשר ליצור קשר dovid@tchumim.com

                          yossizY תגובה 1 תגובה אחרונה
                          1
                          • yossizY מנותק
                            yossizY מנותק
                            yossiz
                            השיב לdovid ב נערך לאחרונה על ידי yossiz
                            #18

                            @dovid אמר בקריאה אסינכרונית משרת HTTP בGO:

                            שהרי כשאתה משים משתנה מחרוזת למשתנה אחר זה כן כתובת שונה ותוכן בלתי תלוי, אז מה זה שונה

                            הבייטים של המחרוזת הם אותם בייטים, אממה הכתובת של המשתנה הוא לא הכתובת של הבייטים אלא הכתובת של המצביע. (ומכיון ש-string ב-GO הוא immutable - בלי משחקים ב-unsafe - זה בטוח לגמרי, אבל ברגע שמשחקים עם הבייטים עצמם על ידי קוד unsafe כאן יתחילו בעיות)

                            תעשה ניסוי קטן:

                            package main
                            
                            import (
                            	"fmt"
                            	"unsafe"
                            )
                            
                            func main() {
                            	b := []byte("ABC")
                            	s := *(*string)(unsafe.Pointer(&b))
                            	s2 := s
                            	s3 := string(b) // this is safe (it makes a copy of the original bytes)
                            	b[0] = 'X'
                            	b[1] = 'Y'
                            	b[2] = 'Z'
                            	fmt.Println(s)
                            	fmt.Println(s2)
                            	fmt.Println(s3) 
                            }
                            

                            https://play.golang.org/p/EpPwsoM-z8g

                            זה פחות או יותר מה שקורה בתוכנה של @nigun

                            📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                            nigunN תגובה 1 תגובה אחרונה
                            2
                            • nigunN מנותק
                              nigunN מנותק
                              nigun
                              השיב לyossiz ב נערך לאחרונה על ידי nigun
                              #19

                              @yossiz
                              נראה לי שעכשיו הבנתי קצת מה קורה כאן
                              המשתנה number הוא מערך של bytes
                              והוא מצביע על מערך אחר של bytes (שנמצא בCtx ???)
                              כשאני מדפיס בפעם הראשונה את number זה מיד אחרי שקראתי לfunc(c *fiber.Ctx)
                              ולפני שנעשה קריאה חדשה לfunc(c *fiber.Ctx)
                              ולכן הוא מדפיס את הערך האחרון (שהוא הנכון)
                              אבל בפעם השניה הערך הוא המערך במקורי הוא [57 49] שזה "19" (כי זה היה הקריאה האחרונה)
                              אבל המצביע number נשאר עם מערך של איבר אחד
                              לכן הוא ממיר לסטרינג רק את האיבר הראשון במערך ולכן הוא מדפיס "1"
                              עד שהוא מגיע למערכים בני שני איברים (כי בקריאה המקורית זה היה עם שני ספרות) ואז הוא מדפיס "19"
                              והסיבה שהוא נשאר מצביע ולא משתנה חדש זה בגלל שהוא נוצר עם unsafe (שנשאר להצביע תמיד על הכתובת הראשונה)

                              `

                              מייל: nigun@duck.com

                              yossizY תגובה 1 תגובה אחרונה
                              2
                              • dovidD מנותק
                                dovidD מנותק
                                dovid ניהול
                                כתב ב נערך לאחרונה על ידי dovid
                                #20

                                @yossiz אכן הבנתי יותר (לא לגמרי כי אני ממש לא מחבב ביטים וברזלים). לפ"ז שרשור או הפונקציה string פותרת את הבעיה בדוגמה של @nigun.
                                תודה רבה!
                                עכ"פ אפשר לסכם שטעות של מתכנת בספריה בגו יכולה לשבש קוד בכל הפרוייקט שמשתמש בה.

                                מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

                                בכל נושא אפשר ליצור קשר dovid@tchumim.com

                                nigunN תגובה 1 תגובה אחרונה
                                2
                                • nigunN מנותק
                                  nigunN מנותק
                                  nigun
                                  השיב לdovid ב נערך לאחרונה על ידי
                                  #21

                                  @dovid אמר בקריאה אסינכרונית משרת HTTP בGO:

                                  עכ"פ אפשר לסכם שטעות של מתכנת בספריה בגו יכולה לשבש קוד בכל הפרוייקט שמשתמש בה.

                                  ובשפות אחרות לא יכול להיות כזה מצב?

                                  מייל: nigun@duck.com

                                  dovidD תגובה 1 תגובה אחרונה
                                  0
                                  • dovidD מנותק
                                    dovidD מנותק
                                    dovid ניהול
                                    השיב לnigun ב נערך לאחרונה על ידי dovid
                                    #22

                                    @nigun עד כמה שחשבתי עד היום...
                                    אני כעת בדקתי וזה אכן קורה גם בC#:

                                    void Main()
                                    {
                                        var t = new strTest();
                                        var str = t.GetStr();
                                        t.Change();
                                        Console.WriteLine(str);
                                    }
                                    
                                    
                                    public class strTest
                                    {
                                        string str = "ABC";
                                    
                                        public string GetStr() => str;
                                    
                                        public void Change()
                                        {
                                            unsafe
                                            {
                                                fixed (char* p = str)
                                                    p[0] = 'a';
                                            }
                                        }
                                    }
                                    

                                    אכן לא תמיד חוסר ידע שלי זה אשמת השפות 🙂

                                    מנטור אישי למתכנתים (ולא רק) – להתקדם לשלב הבא!

                                    בכל נושא אפשר ליצור קשר dovid@tchumim.com

                                    תגובה 1 תגובה אחרונה
                                    3
                                    • yossizY מנותק
                                      yossizY מנותק
                                      yossiz
                                      השיב לnigun ב נערך לאחרונה על ידי
                                      #23

                                      @nigun אמר בקריאה אסינכרונית משרת HTTP בGO:

                                      נראה לי שעכשיו הבנתי קצת מה קורה כאן

                                      בגדול אני מסכים עם ההסבר למרות שהייתי משנה טיפה את הנוסח.

                                      המשתנה number הוא מערך של bytes
                                      והוא מצביע על מערך אחר של bytes

                                      המשתנה number הוא string. ותמיד משתנה מסוג string ב-GO הוא struct שיש בו 2 שדות, 1) מצביע לבייטים של המחרוזת 2) מספר שאומר מה אורך המחרוזת (כמה בייטים לקחת מהמיקום ששדה 1 מצביע עליו)
                                      עכשיו, אם אתה מייצר סטרינג רגיל, סביבת ההרצה דואגת שהסטרינג יהיה immutable (שאין דרך - בלי קוד unsafe - לשנות את הערכים של הבייטים שעליו הוא מצביע), ולכן, אם סביבת ההרצה רואה שיצרת ממערך בייטים שהוא הרי mutable, הוא ידאוג להעתיק את הבייטים המקוריים למיקום חדש שלא ניתן לגישה מקוד רגיל, אבל אם ייצרת אותו ממערך בייטים עם המילה unsafe זה כאילו להגיד לסביבת ההרצה "אל תדאג, אני יודע מה אני עושה, אל תתערב לי" ואז אפשר לשנות את המערך המקורי ובזה לשנות את המחרוזת שהסטרינג ידפיס.

                                      עד שהוא מגיע למערכים בני שני איברים (כי בקריאה המקורית זה היה עם שני ספרות) ואז הוא מדפיס "19"

                                      יפה! לנקודה הזאת לא שמתי לב, זה החלק שהיה חסר לי בפאזל.

                                      והסיבה שהוא נשאר מצביע ולא משתנה חדש זה בגלל שהוא נוצר עם unsafe

                                      לא ממש מדוייק, תמיד string יהיה מצביע, אבל כנ"ל באריכות, ההבדל הוא אם הוא מצביע למיקום שנגיש מקוד אחר או לא

                                      מקוה שהסברתי ברור...

                                      📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                                      nigunN תגובה 1 תגובה אחרונה
                                      3
                                      • yossizY מנותק
                                        yossizY מנותק
                                        yossiz
                                        השיב לnigun ב נערך לאחרונה על ידי yossiz
                                        #24

                                        @nigun אמר בקריאה אסינכרונית משרת HTTP בGO:

                                        בדקתי עכשיו מה קורה אם אני משתמש בשרת fasthttp שעליו פיבר מבוסס (הוא גם משתמש בctx)
                                        ושם התוצאה תקינה
                                        כנראה שהבעיה היא בפיבר

                                        עיין כאן איך אפשר לממש את הבאג גם ב-fasthttp.
                                        הדוגמה שלך עובד טוב כי הפונקציה string מייצר string חדש בצורה בטוחה.

                                        @dovid אמר בקריאה אסינכרונית משרת HTTP בGO:

                                        אני כעת בדקתי וזה אכן קורה גם בC#:

                                        השימוש במילת unsafe מחזיר אותך אחורה לימי ה-C שיש לך שליטה מלאה על כל מרחב הזיכרון של התהליך
                                        ב-JS אסור בהחלט שזה יקרה כי כך יהיה אפשר לפרוץ החוצה מארגז החול של הדפדפן, ולכן באמת אין unsafe ב-JS.

                                        📧 יוסי@מייל.קום | 🌎 בלוג | ☕ קפה

                                        תגובה 1 תגובה אחרונה
                                        3
                                        • nigunN מנותק
                                          nigunN מנותק
                                          nigun
                                          השיב לyossiz ב נערך לאחרונה על ידי
                                          #25

                                          @yossiz אמר בקריאה אסינכרונית משרת HTTP בGO:

                                          לא ממש מדוייק, תמיד string יהיה מצביע, אבל כנ"ל באריכות, ההבדל הוא אם הוא מצביע למיקום שנגיש מקוד אחר או לא

                                          תודה על כל ההסברים המדוייקים
                                          רק לא הבנתי את הקטע האחרון
                                          האם כל פעם שאני יוצר סטרינג חדש (רגיל) הוא מצביע לכתובת חדשה, ואם משתמשים בunsafe אז הוא מצביע על המקורי
                                          או שזה עובד אחרת?

                                          מייל: nigun@duck.com

                                          yossizY תגובה 1 תגובה אחרונה
                                          0

                                          • 1
                                          • 2
                                          • 3
                                          בא תתחבר לדף היומי!
                                          • התחברות

                                          • אין לך חשבון עדיין? הרשמה

                                          • התחברו או הירשמו כדי לחפש.
                                          • פוסט ראשון
                                            פוסט אחרון
                                          0
                                          • דף הבית
                                          • קטגוריות
                                          • פוסטים אחרונים
                                          • משתמשים
                                          • חיפוש
                                          • חוקי הפורום