Programistyczne WTF jakie Was spotkały

5

Kolejny dekoderowy WTF, tym razem eBOX M15, przystosowany do telewizji światłowodowej.

Problem stanowi funkcja nagrywania programów. Dekoder ten nie posiada wbudowanego dysku, więc nagrywane programy najpewniej przechowywane są na serwerach dostawcy łącza internetowego. Z jednej strony dobre rozwiązanie, bo taka usługa pozwala również cofać program bądź ile (dwa dni maksymalnie), jednak minusem jest ”czas życia” nagrania – po siedmiu dniach jest kasowane.

Jednak dekoder posiada gniazdo USB do podłączenia zew. nośnika, o pojemności od 8GB do 500GB. Z racji tej, że i tak programów nie nagrywa się nie wiadomo ile, zanim przyszli monterzy od usługodawcy, zakupiło się pendrive o pojemności 32GB – w zupełności wystarczy. Po montażu ząbek sformatowałem, podpinam – nie widzi go. Formatuję jeszcze raz, zmieniając system plików i znów nic.

I tu pojawia się owy WTF – wg pracowników/monterów usługodawcy, dekoder obsługuje zewnętrzne nośniki wyłącznie z własnym zasilaniem, albowiem gniazdo USB obsługuje piny D+ i D-, ale VCC i GND już nie… :|


Edit: Czas na naprostowanie, albowiem każdy wie swoje, a nikt nie zna prawdy. Okazuje się, że ten dekoder w ogóle nie obsługuje zewnętrznych nośników – port USB jest kompletnie nieczynny i żadne cuda z dostarczaniem zasilania nic nie dadzą. Informacja potwierdzona u samego dystrybutora tych dekoderów.

Pozostaje tylko pytanie – po co ten port w ogóle istnieje, skoro nie jest obsługiwany? Znając praktyki stosowane przez producentów, zapewne linia produkcyjna przystosowana jest do montażu kilku modeli – jedne dostają wsparcie zew. nośników (zapewne droższe), a inne nie. ;)

4

GoogleHasBeenBroken.png
No WTF, skoro te plusy kiedyś działały, to czemu teraz są ignorowane.
To była fajna opcja dla zaawansowanych użytkowników, a teraz?

Nie mógł mi po prostu napisać jasno na początku, że nie ma pasujących wyników i że próbuje ignorując różne parametry?

3

Kilka dni temu cieszyłem się, że po aktualizacji Lazarusa, żaden projekt nie posypał się. No i zapeszyłem – o ile projekty ładnie się skompilowały, o tyle główna aplikacja przestała działać jak wcześniej…

Projekt zbudowany jest na podstawie własnej biblioteki komponentów, w której wszystkie własne kontrolki dziedziczą z bazowych klas z LCL. Jeśli coś zostanie zmienione w kodzie bazowych klas, to zmiany te będą obowiązywać w klasach moich kontrolek. Widać podłubali coś w LCL, bo:

  1. Był problem z wykrywaniem momentu wyjechania kursorem poza obszar komponentu. Gdy kontrolka była blisko krawędzi okna, szybkie wyjechanie kursorem poza okno nie powodowało wysłania przez okno komunikatu CM_MOUSELEAVE, w efekcie czego kontrolka dalej wyglądała tak, jakby kursor nadal się nad nią znajdował. Dorobiłem do tego łatkę – w bazowej klasie dla wszystkich kontrolek był timer, który sprawdzał pozycję kursora i jeśli okno nie zorientowało się, że kursor jest już poza kontrolką, timer wysyłał ten komunikat zamiast okna. Działało, teraz działa i bez niego – łatka niepotrzebna.
  2. Był problem z renderowaniem kontrolki w designerze, jeśli posiadała wysokość lub szerokość równą 1px. W designerze rysowana była dziwna niebieska linia wzdłóż komponentu, podczas działania jej nie było – czyli to bug designera. Dodałem łatkę ustawiającą rozmiar komponentu na 2px, ale tylko w designerze. Ten problem również rozwiązano i łatka nie jest potrzebna.

Zbędne łatki usunąłem z kodu i jeśli o te sprawy chodzi to wszystko fajnie działa. Ale jest nowy problem – teraz każdy komponent, nad którym obrócę rolkę myszy, dostaje w gratisie komunikat CM_MOUSELEAVE… :|

Jest to potwierdzone – postawiłem breakpoint w handlerze tego komunikatu i po samym obróceniu kółka myszy, program zatrzymuje się na tej linijce. Najlepsze jest to, że w kodzie całej swojej biblioteki kontrolek (ok. 20kLoC), komunikat CM_MOUSELEAVE wysyłam tylko w dwóch metodach, i które to nie mają absolutnie nic wspólnego z myszą. Nawet jeśli te linijki (czy całe metody) zaremuję to i tak te komunikaty przylatują i każdą kontrolkę w ten sposób deaktywują… Cały dzień siedzę nad tym i szukam przyczyny, ale kompletnie nic nie znalazłem.

Coś czuję, że znalezienie przyczyny zajmie mi z tydzień… :/

0
MarekR22 napisał(a):

GoogleHasBeenBroken.png
No WTF, skoro te plusy kiedyś działały, to czemu teraz są ignorowane.
To była fajna opcja dla zaawansowanych użytkowników, a teraz?

Nie mógł mi po prostu napisać jasno na początku, że nie ma pasujących wyników i że próbuje ignorując różne parametry?

Ale jaki sens w zwracaniu pustej listy, skoro bez plusa są wyniki? I jasno ci pokazał, że wyniki bez plusa. IMO czepiasz się.

0

Centos 6.8 nie wspiera natywnie pythona2.7 i trzeba sie sporo nameczyc zeby go tam miec i nie rozwalic systemu.

https://danieleriksson.net/2017/02/08/how-to-install-latest-python-on-centos/

1

Kojarzycie taką gierkę Pekka Kanna 2? Swego czasu była popularna i dzisiaj odkopałem na githubie klona umożliwiającego skompilowanie go na linuxie.

https://github.com/danilolc/pk2/blob/master/src/pk2.cpp

//This code does everything,

...

0

Jenkins - wiem ze to da sie zrobic przy uzyciu pluginow, ale trzeba miec uprawnienia (na szczescie mam, ale i tak mnie to denerwuje).
Domyslnie nie ma opcji resetu/wylaczenia Jenkinsa.
Nie ma opcji zrobienia backupu jobow.
I co mnie najbardziej denerwuje wyboru jezyka. Wiec jesli ktos nie moze zmieniac konfiguracji to zeby nie miec mixu polsko-angielskiego musi zmeiniac jezyk w przegladarce.

To tak jakby kupic samochod np. bez jednej swiecy, reflektora i stacyjki :)

0
WhiteLightning napisał(a):

Centos 6.8 nie wspiera natywnie pythona2.7 i trzeba sie sporo nameczyc zeby go tam miec i nie rozwalic systemu.

https://danieleriksson.net/2017/02/08/how-to-install-latest-python-on-centos/

To jeszcze nic. CentOS nie wspiera aktualizacji do wyższej wersji:

The link to the tool has been removed from this page as it is now so broken that it will almost certainly damage your system.

https://wiki.centos.org/TipsAndTricks/CentOSUpgradeTool

P.S.
Cent-OS nie wspiera prawie niczego natywnie, rozumiem, jak ktoś stawia na CentOS-ie bazę Oracle, albo coś, co wspiera tylko RedHat-a. W większości przypadków CentOS to dodawanie sobie roboty.

8
furious programming napisał(a):
srsly napisał(a):

Kojarzycie taką gierkę Pekka Kanna 2? Swego czasu była popularna i dzisiaj odkopałem na githubie klona umożliwiającego skompilowanie go na linuxie.

Kod po fińsku. Nie no zajebiście…

Najlepsze tam jest to:

int RUUDUN_LEVEYS  = SCREEN_WIDTH;
int RUUDUN_KORKEUS = SCREEN_HEIGHT;

czyli

int SZEROKOSC_EKRANU = SCREEN_WIDTH;
int WYSOKOSC_EKRANU  = SCREEN_HEIGHT;
8

Zastanawiałem się, czemu miejscowość się nie zapisuje...

<div class="eight wide field">
    <label for="cipy">Miejscowość</label>
    <input name="company[address][cipy]" id="cipy" type="text">
</div>
21

Dzisiaj WTF z serii co wyjdzie, jak dwa zespoły optymalizują współdziałający kod niezależnie od siebie:

  1. Zespół od sterownika bazy danych: "Zróbmy to nowocześnie! Niech nie będzie blokowania! Postawmy to na Netty! Niech zapytania latają asynchronicznie! Skoro są asynchorniczne, to możemy puścić je na jednym połączeniu oszczedzając sockety, wątki, bufory, itp. I możemy nawet je scalać/kompresować w locie, aby zaoszczędzić przepustowość. A użytkownicy nie muszą pisać aplikacji wielowątkowych aby wykonywać zapytania równolegle, oszczędzając czas na debugowanie. Wszyscy wygrywają!"

  2. Zespół od silnika bazy danych: "Zróbmy to nowocześnie! Pule wątków i executory są takie passe. Zamienimy to na Netty. Niech każde żądanie jest obsługiwane asynchronicznie przez detykowany wątek z jednej puli wątków. Jedna pula wątków na wszystko. Niech wątków będzie tyle co rdzeni. Wyeliminować context-switche. Opóźnienia to nasz wróg. Przypniemy wątki do rdzeni aby zmaksymalizować użycie cache. Niech jeden wątek dba o dane połączenie i obsługuje żądanie od początku do końca. Wyeliminujmy niepotrzebną synchronizację. I zróbmy AIO wszędzie, aby nigdy blokować! Ale będzie śmigać!"

Efekt:
Łączysz się z serwerem baz danych o 48 rdzeniach. Wysyłasz 10000 zapytań asynchronicznie i... wszystkie wykonują się na jednym wątku, jednym rdzeniu, a pozostałe rdzenie są idle. :D

2

Java pozwala na switch(String). Ale jak argument switcha będzie nullem, to default się nie wykona, cala instrukcja switch zostanie zignorowana, a kompilator nie wygeneruje nawet ostrzeżenia.

2

@Szalony Krawiec mijasz się z prawdą. https://ideone.com/WzH3yp.

0

Leci NullPointerException:

java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.hashCode()' on a null object reference

Swoją drogą, logika by nakazywała aby wykonał się blok pod default a tak nulla trzeba sprawdzać oddzielnie przed switchem

0

@aurel to zależy od punktu widzenia. Skoro zmienna jest nullem to nie spełnia żadnego warunku switcha. Jak nie jest zainicjowna, czyli jest nullem to nie ma żadnej wartości.

Na upartego można tak:

switch(a==null?"":a)
{ 
  ....
}

A najlepiej nie odwoływać się do niezainicjalizowanej zmiennej.

2

To, że masz NPE wynika z założeń języka. Odwołanie do null zawsze powoduje błąd. Ma to uzasadnienie w bezpieczeństwie języka.

1

Gdy wyszukujemy kiosk na stronie Paczka w Ruchu, system jest na tyle uczynny że podaje odległość kiosku z miejscowości X do niej samej.

screenshot-20180108193151.png

3
if (false === $area) {
    echo "WTF?", PHP_EOL;
    return;
}

:D

13

Krótko i na temat:

Constants.loadProperties()
1
function wtf(value) {
  console.log(value, arguments);

  value = 2;

  console.log(value, arguments);
}

wtf(1);

// 1 [1, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 2 [2, callee: ƒ, Symbol(Symbol.iterator): ƒ]

Spodziewałbym się, że tablica arguments będzie trzymała kopię argumentów przekazanych do funkcji, tak żeby zawsze to były te, które zostały przekazane, nie ważne co się z nimi później stanie. Zwłaszcza, że przekazujemy typ prosty. Gdybym zrobił coś takiego:

function wtf(value) {
  console.log(value, arguments);

  value.foo = 2;

  console.log(value, arguments);
}

wtf({foo: 1});

to jeszcze bym zrozumiał, dlaczego arguments uległo zmianie... ale tak?

A oto kod pewnej biblioteki, przez którą się tego nauczyłem:

var oldCallback = callback;
callback = function (value) {
    // ... 

    if (elements) {
        // tutaj w pewnym momencie jest robione
        value = [value];
    }

    // ... 
    if (oldCallback) {
        // oldCallback to funkcja definiowana przez klienta biblioteki, 
        // wiec mialem niezly WTF jak w moim callbacku parametr 
        // powinien byc stringiem, a był tablicą :D
        oldCallback.apply(target, arguments); 
    }
};
18

Aplikacja sterująca systemem ticketowym na parkingu Centrum Nowoczesnych Technologii lokalnego uniwerku pisana w Javie.
Generalna zasada:: podjazd samochoduy generuje autowydruk biletu oraz trigeruje SQL do bazy w stylu insert <numer-rej-samochodu> into <nazwa-tabeli>
Działa. Ale......
Dział Utrzymania Technicznego chce jakiejś tam zmiany w aplikacji bo coś tam nie teges i wali błędami. No ok - zaghlądam do kodu, a tam (uwaga mocne)

this.setTitle = "ParkingTytul";

wywołanie funkcji odpowiedzialnej za druk biletu:

zjebanaMaszyna.wywalBiet(true);

I więcej takowych

6

Pewien web service chwali się, że ich api współpracuje z Rest/json i Soap.
A w praktyce po rest leci Xml z Soap opakowany w Json.

Request content:

{
  "authorizationXml" : "<authorization>...<\/authorization>",
  "sspFileId" : "MyFileId" 
}
Request response:

{
  "d": "<apiResult>...</apiResult>" 
}
2

screenshot-20180131214838.png
Windows chłoszcze..

17

Nadchodzi nowy semestr, z tej okazji podzielę się dziełem pewnego wykładowcy Politechniki Wrocławskiej. Tenże pan doktor przedstawił w trakcie wykładu rewelacyjny sposób na optymalizację iterowania po tabeli w Javie. Nasze zadanie: Zsumować wartości 30-elementowej tablicy intów.

Optymalizacja 1. Iterować z góry na dół.

int[] tab = new int[30];
//...
int sum = 0;
for (int i = tab.length; i > 0; i--) {
	sum += tab[i-1];
}

Jest to jeszcze względnie normalne i z tego, co się orientuję, faktycznie JVM ogarnia taką optymalizację szybciej, podobnie słuszne to było w przypadku starych komputerów, których architektura pozwalała na porównywanie tylko do zera, przez co a < b było przemieniane na (a-b) < 0. Wątpię jednak, aby różnica, zwłaszcza w dzisiejszych czasach, była bardzo zauważalna, za to pozwala to studentom na żarciki - bo przecież to naturalne, że z góry na dół jest szybciej, bo nam wtedy pomaga grawitacja ;)

Optymalizacja 2. Przeskakiwanie iteracji.

int[] tab = new int[30];
//...
int sum = 0;
for (int i = 0; i < tab.length; i+=3) {
	sum += tab[i];
	sum += tab[i+1];
	sum += tab[i+2];
}

Owszem, oszczędzamy na ilości wykonań pętli, ale dostajemy potworka, którego łatwo zepsuć - wystarczy zmienić założenia i stwierdzić, że tablica ma mieć np. 100 elementów (albo 200 albo dowolną inną ilość niepodzielną przez 3) i dostaniemy w nagrodę wyjątek wykroczenia poza zakres tablicy. A jeżeli zakładamy, że liczba elementów jest stała, to po co w ogóle robić pętlę, skoro można by skopiować dodawanie 30 razy? ;)
(to akurat słaby żart, bo ten doktorek faktycznie sugeruje coś takiego)

"Optymalizacja" 3. Bez komentarza

int[] tab = new int[30];
//...
int sum = 0;
try {
	int i = 0;
	while(true) {
		sum += tab[i];
		i++;
	}
} catch (ArrayIndexOutOfBoundsException ex) {}
0
ShookTea napisał(a):

W assemblerze każda iteracja pętli ma warunek, czyli ten drugi przykład to serio miał optymalizację, gościu nawet nie taki głupi.

0

Właściwie te pierwsze to bym spekulował, bo dodawanie i odejmowania zajmuje tyle samo cykli procesora.

Ale te ostatnie hmm, zakładając, że kompilator zoptymalizował i nie sprawdzał co iterację czy TRUE jest równe TRUE, to mogło zaoszczędzić ilość sprawdzeń równych ilości elementów.
Lecz same zabezpieczenie stosu samo w sobie robi w sumie to samo, co taka pętla, czyli trochę marnuje procka byciem zbyt opiekuńczym dla programisty, który wiadomo wie co robi....

5

@Afish @spartanPAGE @satirev
Szersza historia, może posłużyć za sprostowanie:
Prowadzący nie jest ten sam, co we wcześniejszych postach.
"Optymalizacja" była przedstawiona tak po prostu, bez zaglądania do bajtkodu i tłumaczenia, dlaczego tak, a nie inaczej, poza skróconym "mniej pojedynczych operacji = lepiej", co miało wyjaśniać lepszą wydajność punktu trzeciego.
Cały slajd o optymalizacji był jedną z ciekawostek - porad, jak pisać dobry kod. Porady te były zaskakująco zbliżone do tych z tej strony: https://github.com/Droogans/unmaintainable-code
Porady te były pokazywane równolegle do głównej tematyki wykładu, jaką są "Systemy Wbudowane i Mobilne". Wbrew pięknej nazwie, tematyką SWiM jest robienie apek na telefony w J2ME, gdzie za dobrą apkę jest uznawana taka mająca możliwie jak najwięcej kontrolek (choćby nawet nic nie robiły - im więcej, tym lepiej) oraz najróżniejsze kolory obok siebie.

EDIT:
Jednocześnie na innym slajdzie dowiadujemy się w ramach optymalizacji pamięci, aby:

  1. nie używać obrazków w apkach, bo zajmują dużo pamięci (więc co zrobić, gdy obrazek musi być?)
  2. zawsze używać zmiennych publicznych, bo oszczędza pamięć i jest szybsze
  3. Nie używać klasy Date i tym podobnych, bo zajmuje strasznie dużo miejsca i lepiej trzymać dane w long
  4. "Wyrzucanie i łapanie wyjątków jest bardzo (!) kosztowne" (sic, idealnie pasuje do jego trzeciej optymalizacji pętli)
3

Od dwóch dni szukam dziwnego błędu, który powoduje, że czasem na środowisku testowym, tak gdzieś raz na tydzień, gubi nam się jakiś element Observable. Dodam, że lokalnie w testach na laptopie nie udało się błędu powtórzyć.

Dzisiaj mym oczom ukazał się poniższy kod:



        /**
         * The standard emission loop serializing events and requests.
         */
        void emitLoop() {
            boolean skipFinal = false;
            try {
                final Subscriber<? super T> child = this.child;
                for (;;) {
                    // eagerly check if child unsubscribed or we reached a terminal state.
                    if (checkTerminate()) {
                        skipFinal = true;
                        return;
                    }
                    Queue<Object> svq = queue;

                    long r = producer.get();
                    boolean unbounded = r == Long.MAX_VALUE;

                    // count the number of 'completed' sources to replenish them in batches
                    int replenishMain = 0;

                    // try emitting as many scalars as possible
                    if (svq != null) {
                        for (;;) {
                            int scalarEmission = 0;
                            Object o = null;
                            while (r > 0) {
                                o = svq.poll();
                                // eagerly check if child unsubscribed or we reached a terminal state.
                                if (checkTerminate()) {
                                    skipFinal = true;
                                    return;
                                }
                                if (o == null) {
                                    break;
                                }
                                T v = NotificationLite.getValue(o);
                                // if child throws, report bounce it back immediately
                                try {
                                    child.onNext(v);
                                } catch (Throwable t) {
                                    if (!delayErrors) {
                                        Exceptions.throwIfFatal(t);
                                        skipFinal = true;
                                        unsubscribe();
                                        child.onError(t);
                                        return;
                                    }
                                    getOrCreateErrorQueue().offer(t);
                                }
                                replenishMain++;
                                scalarEmission++;
                                r--;
                            }
                            if (scalarEmission > 0) {
                                if (unbounded) {
                                    r = Long.MAX_VALUE;
                                } else {
                                    r = producer.produced(scalarEmission);
                                }
                            }
                            if (r == 0L || o == null) {
                                break;
                            }
                        }
                    }

                    /*
                     * We need to read done before innerSubscribers because innerSubscribers are added
                     * before done is set to true. If it were the other way around, we could read an empty
                     * innerSubscribers, get paused and then read a done flag but an async producer
                     * might have added more subscribers between the two.
                     */
                    boolean d = done;
                    // re-read svq because it could have been created
                    // asynchronously just before done was set to true.
                    svq = queue;
                    // read the current set of inner subscribers
                    InnerSubscriber<?>[] inner = innerSubscribers;
                    int n = inner.length;

                    // check if upstream is done, there are no scalar values
                    // and no active inner subscriptions
                    if (d && (svq == null || svq.isEmpty()) && n == 0) {
                        Queue<Throwable> e = errors;
                        if (e == null || e.isEmpty()) {
                            child.onCompleted();
                        } else {
                            reportError();
                        }
                        skipFinal = true;
                        return;
                    }

                    boolean innerCompleted = false;
                    if (n > 0) {
                        // let's continue the round-robin emission from last location
                        long startId = lastId;
                        int index = lastIndex;

                        // in case there were changes in the array or the index
                        // no longer points to the inner with the cached id
                        if (n <= index || inner[index].id != startId) {
                            if (n <= index) {
                                index = 0;
                            }
                            // try locating the inner with the cached index
                            int j = index;
                            for (int i = 0; i < n; i++) {
                                if (inner[j].id == startId) {
                                    break;
                                }
                                // wrap around in round-robin fashion
                                j++;
                                if (j == n) {
                                    j = 0;
                                }
                            }
                            // if we found it again, j will point to it
                            // otherwise, we continue with the replacement at j
                            index = j;
                            lastIndex = j;
                            lastId = inner[j].id;
                        }

                        int j = index;
                        // loop through all sources once to avoid delaying any new sources too much
                        for (int i = 0; i < n; i++) {
                            // eagerly check if child unsubscribed or we reached a terminal state.
                            if (checkTerminate()) {
                                skipFinal = true;
                                return;
                            }
                            @SuppressWarnings("unchecked")
                            InnerSubscriber<T> is = (InnerSubscriber<T>)inner[j];

                            Object o = null;
                            for (;;) {
                                int produced = 0;
                                while (r > 0) {
                                    // eagerly check if child unsubscribed or we reached a terminal state.
                                    if (checkTerminate()) {
                                        skipFinal = true;
                                        return;
                                    }
                                    RxRingBuffer q = is.queue;
                                    if (q == null) {
                                        break;
                                    }
                                    o = q.poll();
                                    if (o == null) {
                                        break;
                                    }
                                    T v = NotificationLite.getValue(o);
                                    // if child throws, report bounce it back immediately
                                    try {
                                        child.onNext(v);
                                    } catch (Throwable t) {
                                        skipFinal = true;
                                        Exceptions.throwIfFatal(t);
                                        try {
                                            child.onError(t);
                                        } finally {
                                            unsubscribe();
                                        }
                                        return;
                                    }
                                    r--;
                                    produced++;
                                }
                                if (produced > 0) {
                                    if (!unbounded) {
                                        r = producer.produced(produced);
                                    } else {
                                        r = Long.MAX_VALUE;
                                    }
                                    is.requestMore(produced);
                                }
                                // if we run out of requests or queued values, break
                                if (r == 0 || o == null) {
                                    break;
                                }
                            }
                            boolean innerDone = is.done;
                            RxRingBuffer innerQueue = is.queue;
                            if (innerDone && (innerQueue == null || innerQueue.isEmpty())) {
                                removeInner(is);
                                if (checkTerminate()) {
                                    skipFinal = true;
                                    return;
                                }
                                replenishMain++;
                                innerCompleted = true;
                            }
                            // if we run out of requests, don't try the other sources
                            if (r == 0) {
                                break;
                            }

                            // wrap around in round-robin fashion
                            j++;
                            if (j == n) {
                                j = 0;
                            }
                        }
                        // if we run out of requests or just completed a round, save the index and id
                        lastIndex = j;
                        lastId = inner[j].id;
                    }

                    if (replenishMain > 0) {
                        request(replenishMain);
                    }
                    // if one or more inner completed, loop again to see if we can terminate the whole stream
                    if (innerCompleted) {
                        continue;
                    }
                    // in case there were updates to the state, we loop again
                    synchronized (this) {
                        if (!missed) {
                            skipFinal = true;
                            emitting = false;
                            break;
                        }
                        missed = false;
                    }
                }
            } finally {
                if (!skipFinal) {
                    synchronized (this) {
                        emitting = false;
                    }
                }
            }
        }>

Obstawiam, że to gdzieś tu. Notabene, w innym 5x prostszym kodzie tego samego projektu znalazłem dzisiaj race-condition.

W przeciwieństwie do kodu, komit, który to dodał, ma bardzo skrócony opis:
"Merge fully rewritten and other related optimizations" :D

0

Wrzucam to w tym wątku, bo dla mnie to WTF, że przez pozew jednej firmy tracą użytkownicy.

Google wywaliło "View image" z wyszukiwarki zdjęć. Nie ma już bezpośredniego przejścia do zdjęcia pełnej jakości :(

2 użytkowników online, w tym zalogowanych: 1, gości: 1